源码分析之七大设计原则
Posted 学无止路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析之七大设计原则相关的知识,希望对你有一定的参考价值。
1..1掌握设计模式的度?
掌握设计模式的四个层次:
1. 没学之前是一点不懂,根本想不到用设计模式,写出的代码很糟糕;
2. 学了几个模式之后,于是总想着要用自己学过的模式,于是时常造成误用模式而自己却不知道;
3. 学完全部的模式之后,感觉模式之间都很相似,分不清差异,有困惑,但深知误用设计模式的害处,应用之时很犹豫。
4. 灵活应用模式,甚至不应用具体的某种模式也能设计出非常优秀的代码,以达到无剑胜有剑的境界。
1.2关于设计模式的一些理解
1. 设计模式,不是一种新的语言,也不是什么新的API,更不是什么新的语法。
2. 设计模式,是前辈们,不断总结,不断打磨出的设计方法。不同设计模式适用于不同的场景。
3. 设计模式,公认的有23种,我们延伸到了25种,分别对应25种设计场景。这些设计模式,我们不用怀疑它的功能!
因为这些设计模式是经过长期的实践考验,而留存下来的。
GOF23设计模式 解释器模式 》 开发源码
4. 千万不要认为有任何一种设计模式,能解决任何问题,每一种设计模式只能用于适用的场景,而不是万能的。
5. 设计模式有优点,也有缺点。我们不要为了使用设计模式而使用设计模式。切记防止“模式的滥用”!
6. 25种设计模式,背后其实是7大原则。也就是说,每一个设计模式都归属于一个或多个设计原则。
7. 7大设计原则的背后是一个字 "分"
8. 学习设计模式,脑海中一定要紧绷两根弦。
a. 开发代码的工作者,分为两种角色:
作者(服务器端的程序猿)
用户(客户端程序员)
b. 如果我们没有拥有作者的源代码,能使用但是得不到源码,想改源码改不了,就算我们真的拥有源码,也不能改。因为要符合开闭原则。
设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Designpattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度,使程序呈现高内聚,低耦合的特性。
设计模式并不局限于某种语言,java,php,c++都有设计模式。
一堆优秀代码的集合。
1.3单一职责
单一职责:
每个方法、每个类、每个框架都只负责一件事情
Single Responsibility Principle, SRP
核心思想:解耦和增强内聚性(高内聚,低耦合)
类被修改的几率很大,因此应该专注于单一的功能。如果你把多个功能放在同一个类中,功能之间就形成了关联,改变其中一个功能,有可能中止另一个功能,这时就需要新一轮的测试来避免可能出现的问题。
职责:一个变化的原因。
如果你想多出一种动机来更改一个类,则这个类就多一个职责。
核心思想:化整为零将整体分解为局部的逻辑单元,不同的逻辑单元之间尽可能的相对独立,并且职责明确。
微服务的拆分(原则)比如业务拆分原则
1. 单一职责 高内聚 低耦合。解耦 内聚性。
2. 服务粒度
3. 以业务模型切入。
1.4与类之间的关系 UML
依赖:两个相对独立的对象,当一个对象负责构造另外一个对象的实例或者依赖另外一个对象的服务时,就产生了依赖。
关联:关联通过其对象建立两个单独类之间的关系。
聚合:java中聚合是一种特殊的关联形式,它是两个类之间的关系,是一种HASA关系,是一种单向关联。如果类具有实体引用,则称为聚合。
组合:组合是整体与部分的关系, 并且部分不可以离开整体而单独存在。
继承/泛化:继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
实现:实现要实现的某个类的方法。
1.5 OpenClosed Principle, OCP 开闭原则
核心思想:对扩展开放(对提供方),对修改关闭(对使用方)。
提供方是指构建者。
使用方是指的调用者。
扩展开放:模块添加新功能,不改变原有的代码。
修改关闭:某模块被其他模块调用,如果该模块的源代码不允许修改,则该模块修改关闭的。
通俗理解就是添加一个功能应该是在已有的代码基础上进行扩展,而不是修改已有代码。
开闭原则的最终解决方案是面向接口编程。
面向接口编程的流程图如下:
开闭原则是最基础的设计原则,其他设计原则都是开闭原则的具体形态。
结合笔记所需要创建项目的步骤如下:
1显示打开IDEA的界面,点击CreateNew Project如图所示:、
2.选择Maven工程和JDK的版本,并点击Next。如图所示:
4.打开pom.xml文件,导入依赖jar包的代码如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>Designpattern</artifactId> <version>1.0-SNAPSHOT</version> <name>DesignPattern</name> <!-- FIXME change it tojun the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <!--导入junit单元测试的依赖jar包--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.2</version> <scope>test</scope> </dependency> <dependency> <!--导入spring-webmvc的依赖jar包--> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <!--导入spring-context的依赖jar包--> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> <dependency> <!--导入cglib的依赖jar包--> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.6</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.12.4</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>2.7</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <version>3.8.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> </plugins> </pluginManagement> </build> </project> |
违背开闭原则的代码如下:
package com.txw.principle.openclose.v1;
/** * @author Adair */ @SuppressWarnings("all") // 注解警告信息 public class Client { public static void main(String[] args) { // 使用存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Cyclo()); // 业务需求:绘制三角形 graphicEditor.drawShape(new Triangle()); } } @SuppressWarnings("all") // 注解警告信息 // 这是一个用于绘制图的类【使用方】 class GraphicEditor{ // 接收shape对象,然后根据type,来绘制不同的图形 public void drawShape(Shape s){ if (s.m_type == 1){ drawRectangle(s); }else if (s.m_type == 2){ drawCyclo(s); }else if (s.m_type == 3){ drawTriangle(s); } } // 绘制矩形 public void drawRectangle(Shape r) { System.out.println("绘制矩形"); } // Shape 和 GraphicEditor是什么关系?作为局部变量是依赖关系。 // 绘制圆形 public void drawCyclo(Shape c) { System.out.println("绘制圆形"); } // 绘制三角形 public void drawTriangle(Shape t) { System.out.println("绘制三角形"); } } @SuppressWarnings("all") // 注解警告信息 // shape类 基类 class Shape{ int m_type; // 定义int类型变量m_type } @SuppressWarnings("all") // 注解警告信息 // 矩形 class Rectangle extends Shape{ public Rectangle() { super.m_type = 1; } } @SuppressWarnings("all") // 注解警告信息 // 圆形 class Cyclo extends Shape{ public Cyclo() { super.m_type = 2; } } @SuppressWarnings("all") // 注解警告信息 // 三角形 class Triangle extends Shape{ public Triangle() { super.m_type = 3; } } |
修改后的代码如下:
package com.txw.principle.openclose.v2;
/** * @author Adair */ @SuppressWarnings("all") // 注解警告信息 public class Client { public static void main(String[] args) { // 使用存在的问题 GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Cyclo()); graphicEditor.drawShape(new Triangle()); graphicEditor.drawShape(new Other()); } } @SuppressWarnings("all") // 注解警告信息 // 这是一个用于绘制图的类【使用方】 class GraphicEditor{ // 图形里面直接提供绘画的方法 public void drawShape(Shape s){ s.draw(s); } // Shape 和 GraphicEditor是什么关系?作为局部变量是依赖关系。 } @SuppressWarnings("all") // 注解警告信息 // shape类 基类 abstract class Shape{ int m_type; // 定义int类型变量m_type public abstract void draw(Shape s); } @SuppressWarnings("all") // 注解警告信息 // 矩形 class Rectangle extends Shape { public Rectangle() { super.m_type = 1; } @Override public void draw(Shape s) { System.out.println("绘制矩形"); } } @SuppressWarnings("all") // 注解警告信息 // 圆形 class Cyclo extends Shape{ public Cyclo() { super.m_type = 2; } @Override public void draw(Shape s) { System.out.println("绘制圆形"); } } @SuppressWarnings("all") // 注解警告信息 // 三角形 class Triangle extends Shape{ public Triangle() { super.m_type = 3; } @Override public void draw(Shape s) { System.out.println("绘制三角形"); } } @SuppressWarnings("all") // 注解警告信息 //业务变化:增加其他图形 class Other extends Shape{ public Other() { super.m_type = 4; } @Override public void draw(Shape s) { System.out.println("绘制其他图形"); } } |
16 里氏替换原则
Liskov SubstitutionPrinciple, LSP
核心思想:任何父类出现的地方,子类都可以替代出现。
也就是说,子类对象可以随时随地的替换父类对象,且替换完以后,语法不会报错,业务逻辑也不会出现问题。
所有引用基类的地方必须能透明地使用其子类的对象。
里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过**聚合,组合,依赖**来解决问题。
多态里面的向上转型 匿名内部类都是基于里氏替换原则
虽然子类较父类保证了数据的准确性,但是违背了里氏替换原则的java代码如下:
package com.txw.principle.liskovsubstitution;
import java.util.HashMap; import java.util.Map; /** * TODO {@link LiskovSubstitutionTest} * @Author:Adair */ @SuppressWarnings("all") // 注解警告信息 public class LiskovSubstitutionTest { public static void main(String[] args) { XxxService service = new XxxService(new RedisCentralCacheManagerService()); service.doXxxService("key2"); } } @SuppressWarnings("all") // 注解警告信息 class XxxService { // 缓存 private CacheManagerService cm; public XxxService(CacheManagerService cm) { this.cm = cm; } // 业务 public String doXxxService(String key) { return cm.get(key); } } @SuppressWarnings("all") // 注解警告信息 // 单机JVM的缓存服务 class CacheManagerService { // 没有考虑并发场景 static Map<String, String> cache = new HashMap<>(); static { cache.put("key1", "value1"); } /** * 获取 * @param key * @return 如果缓存返回值为null或者"" 则直接返回null */ public String get(String key) { //只能保证百分之九十九的情况下数据的准确性 String s = cache.get(key); System.out.println("父类没有判断,直接返回 :" +s); return s; } // 添加 public void put(String key, String value) { cache.put(key, value); } } @SuppressWarnings("all") // 注解警告信息 // 基于Redis的集中式缓存管理 class RedisCentralCacheManagerService extends CacheManagerService { // Redis实现 static Map<String, String> redisCache = new HashMap<>(); // 未考虑并发情况,直接使用HashMap当容器 private static Map<String, String> db = new HashMap<>(); static { redisCache.put("key1", "value1"); db.put("key2","value2"); } @Override public String get(String key) { //解决数据准确性问题,比如redis数据缓存失效, // 但是此时定时任务没有触发db重新将数据刷入缓存这百分之一情况 String cacheValue = redisCache.get(key); if(cacheValue == null){ //没有就从数据库里面取 String s = db.get(key); System.out.println("子类做了判断,从数据库查询返回 :" +s); //更新redis redisCache.put(key,s); return s; } System.out.println("子类也有数据,查询返回 :" +cacheValue); return cacheValue; } /** *@param key * @param value * 优化: * put之前做判断 为null则添加 empty * 缓解了DB的压力 不需要selectData(key)了 */ @Override public void put(String key, String value) { //if(setNx("lock_key"+key, value)){ //设置锁超时 // cache.put(key,value); //业务完成,删除锁,放入其他线程 // }else{ //未拿到锁的线程继续调用put方法 //Thread.sleep(100); // put(key, value); //} } /* 结论:虽然子类 较父类 保证了 数据的准确性 但是违背了里氏替换原则 */ } |
使用CacheManagerService()实例对象运行结果如图所示:
使用RedisCentralCacheManagerService就不会上图所示的结果。
为了保证里氏替换原则的java代码如下:
/** * TODO {@link Client} * * @Author:Adair * 方法重写: 返回类型、方法名 方法参数保持 * 方法重写的2个依据: * 1. 子类重写父类的方法时,子类方法的方法修饰符不能比父类的更严格 * 2. 子类重写父类的方法时, 子类方法不能抛出比父类更多的异常 假设不成立 * 为什么【要有这两个依据? 就是为了保证 子类在替换父类对象后,语法不会报错 * 也是就是为了保证里氏替换原则 */ class SuperClass { public void doSomething(){ } } //派生类 class DeriveClass extends SuperClass{ @Override public void doSomething() throws RuntimeException{ } } public class Client { public static void main(String[] args) { SuperClass sc = new DeriveClass(); sc.doSomething(); // 父类不会抛异常 } // 正常的 一点毛病的没有 } |
1.7 依赖倒置原则
Dependence InversionPrinciple, DIP
核心思想:要依赖于抽象,不要依赖于具体的实现 (上层不能依赖于下层,他们都应该依赖于抽象)。
依赖倒转(倒置)的中心思想是面向接口编程。
依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
依赖倒置原则如图所示:
第一个版本的代码如下:
package com.txw.principle.dependencyreverse.v1;
/** * TODO {@link Client} * @Author:Adair * 有钱人喂养动物 */ @SuppressWarnings("all") // 注解警告信息 class RichPerson{ // 人依赖于动物 public void feed(ZangAo za){ System.out.println("开始喂养。。。"); za.eat(); } public void feed(Ostrich os){ System.out.println("开始喂养。。。"); os.eat(); } public void feed(YangTuo yt){ System.out.println("开始喂养。。。"); yt.eat(); } } class ZangAo{ public void eat(){ System.out.println("藏獒一顿吃十几斤肉类"); } } class Ostrich{ public void eat(){ System.out.println("鸵鸟吃植物类为生"); } } class YangTuo{ public void eat(){ System.out.println("羊驼最喜欢吃生玉米"); } } //变化 业务要求富人,在养一头羊驼 // #================================================================= @SuppressWarnings("all") // 注解警告信息 public class Client { public static void main(String[] args) { RichPerson person = new RichPerson(); ZangAo za = new ZangAo(); Ostrich os = new Ostrich(); person.feed(za); person.feed(os); } } |
第二个版本的java代码如下:
package com.txw.principle.dependencyreverse.v2;
/** * TODO {@link Client} *@Author:Adair */ @SuppressWarnings("all") // 注解警告信息 // 改进 抽象 interface Animal{ public void eat(); } @SuppressWarnings("all") // 注解警告信息 //使用方 class RichPerson{ //依赖于抽象 public void feed(Animal animal){ System.out.println("开始喂养。。。"); animal.eat(); } } @SuppressWarnings("all") // 注解警告信息 //具体实现 class ZangAo implements Animal{ @Override public void eat(){ System.out.println("狗啃骨头"); } } @SuppressWarnings("all") // 注解警告信息 //具体实现 class Ostrich implements Animal{ @Override public void eat(){ System.out.println("猫吃鱼。。。"); } } @SuppressWarnings("all") // 注解警告信息 // 来变化: class Gecko implements Animal{ @Override public void eat() { System.out.println("壁虎爱吃蚊子。。。"); } } @SuppressWarnings("all") // 注解警告信息 public class Client { public static void main(String[] args) { RichPerson person = new RichPerson(); ZangAo dog = new ZangAo(); Ostrich cat = new Ostrich(); Gecko gk = new Gecko(); person.feed(dog); person.feed(cat); person.feed(gk); } } |
依赖于抽象的流程图:
DoXxxService实现 InitializingBean,BeanFactoryAware 接口的代码如下:
package com.txw.principle.dependencyreverse.use;
import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; /** * TODO {@link DoXxxService} * @Author:Adair */ @SuppressWarnings("all") // 注解警告信息 public class DoXxxService implements InitializingBean, BeanFactoryAware { //属性 》 赋值 private BeanFactory beanFactory; // 不是我们主动去调,是框架去调的 我们只是去依赖于我们的抽象, // 我们去初始化beanFactory的主动权是容器 @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { // 容器自动帮我们注入 this.beanFactory = beanFactory; } @Override public void afterPropertiesSet() throws Exception { } } |
1.8 接口隔离原则
Interface SegregationPrinciple, ISP
核心思想:不应该强迫客户程序依赖他们不需要使用的方法。
客户端不应该依赖它不需要的接口。
类间的依赖关系应该建立在最小的接口上。
一个接口不需要提供太多的行为,一个接口应该只提供一种对外的功能,不应该把所有的操作都封装到一个接口当中单一在于实现,接口隔离在于抽象。
建立单一接口,不要庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。
缺点:接口太多,单一功能太零散。
跟单一职责原则相似?
1. 单一职责原则注重的是职责,接口隔离原则注重的是对接口依赖的隔离。
2. 单一职责主要约束的是类,其次才是接口的方法,它针对的是程序中的实现和细节,接口隔离主要约束的是接口,主要针对抽象,针对的程序整体框架的构建。
违背接口隔离原则的代码如下:
package com.txw.principle.interfaceisolation.v1;
/** * @author Adair */ @SuppressWarnings("all") // 注解警告信息 // 返例 interface I{ public void method1(); public void method2(); public void method3(); public void method4(); public void method5(); } @SuppressWarnings("all") // 注解警告信息 // 依赖 class A{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method2(); }public void depend3(I i){ i.method3(); } } @SuppressWarnings("all") // 注解警告信息 class B implements I{ @Override public void method1() { System.out.println("类B实现接口I的方法1"); } @Override public void method2() { System.out.println("类B实现接口I的方法2"); } @Override public void method3() { System.out.println("类B实现接口I的方法3"); } // 对于类B来说,method4和method5不是比需的,但是由于接口I中有这两个方法 //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。 @Override public void method4() { } @Override public void method5() { } } class C{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method4(); } public void depend3(I i){ i.method5(); } } @SuppressWarnings("all") // 注解警告信息 class D implements I{ @Override public void method1() { System.out.println("类D实现接口I的方法1"); } //对于类D来说,method2和method3不是必需的,但是由于接口I中有这两个方法, //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。 @Override public void method2() {} @Override public void method3() {} @Override public void method4() { System.out.println("类D实现接口I的方法4"); } @Override public void method5() { System.out.println("类D实现接口I的方法5"); } } @SuppressWarnings("all") // 注解警告信息 public class Client{ public static void main(String[] args){ A a = new A(); a.depend1(new B()); a.depend2(new B()); a.depend3(new B()); C c = new C(); c.depend1(new D()); c.depend2(new D()); c.depend3(new D()); } } |
违背接口隔离原则的流程图:
修改之后的代码如下:
package com.txw.principle.interfaceisolation.v2;
/** * TODO {@link Client} * @Author:Adair */ @SuppressWarnings("all") // 注解警告信息 public class Client {
} @SuppressWarnings("all") // 注解警告信息 interface I1 { public void method1(); } @SuppressWarnings("all") // 注解警告信息 interface I2 { public void method2(); public void method3(); } @SuppressWarnings("all") // 注解警告信息 interface I3 { public void method4(); public void method5(); } @SuppressWarnings("all") // 注解警告信息 class A{ public void depend1(I1 i){ i.method1(); } public void depend2(I2 i){ i.method2(); } public void depend3(I2 i){ i.method3(); } } @SuppressWarnings("all") // 注解警告信息 class B implements I1, I2{ @Override public void method1() { System.out.println("类B实现接口I1的方法1"); } @Override public void method2() { System.out.println("类B实现接口I2的方法2"); } @Override public void method3() { System.out.println("类B实现接口I2的方法3"); } } @SuppressWarnings("all") // 注解警告信息 class C{ public void depend1(I1 i){ i.method1(); } public void depend2(I3 i){ i.method4(); } public void depend3(I3 i){ i.method5(); } } @SuppressWarnings("all") // 注解警告信息 class D implements I1, I3{ @Override public void method1() { System.out.println("类D实现接口I1的方法1"); } @Override public void method4() { System.out.println("类D实现接口I3的方法4"); } @Override public void method5() { System.out.println("类D实现接口I3的方法5"); } } |
接口隔离原则的流程图如下:
1.9 迪米特原则
Demeter Principle 又称最少知道原则。
核心思想:一个对象应当对其他对象有尽可能少的了解,不和陌生人说话(只与直接的朋友通信)。
降低各个对象之间的耦合,提高系统的可维护性。
直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
Java代码如下:
package com.txw.principle.demeter.v1;
/** * TODO {@link Client} * @Author:Adair */ @SuppressWarnings("all") // 注解警告信息 public class Client { public static void main(String[] args) { } } @SuppressWarnings("all") // 注解警告信息 class Computer { public void savaData() { System.out.println("保存数据。。。"); } public void killProcess() { System.out.println("关闭程序。。。"); } public void closeScreen() { System.out.println("关闭屏幕。。。"); } public void stopPower() { System.out.println("停止供电。。。"); } } @SuppressWarnings("all") // 注解警告信息 class Person { // 关联 private Computer cp = new Computer(); //关电脑 public void closeComputer() { cp.savaData(); cp.killProcess(); cp.stopPower(); cp.closeScreen(); } } /* 这样写有问题吗? 关电脑具体的细节谁知道?电脑知道 谁在关?人在关 逻辑紊乱 --》 人关注关机电脑细节吗?不需要关注按钮关闭 */ |
package com.txw.principle.demeter.v2;
/** * TODO {@link Client} * @Author:Adair */ @SuppressWarnings("all") // 注解警告信息 public class Client { } @SuppressWarnings("all") // 注解警告信息 class Computer { private void savaData() { System.out.println("保存数据。。。"); } private void killProcess() { System.out.println("关闭程序。。。"); } private void closeScreen() { System.out.println("关闭屏幕。。。"); } private void stopPower() { System.out.println("停止供电。。。"); } //关机按钮 public void shutDown(){ savaData(); killProcess(); closeScreen(); stopPower(); } } @SuppressWarnings("all") // 注解警告信息 class Person { private Computer cp = new Computer(); // 按 按钮,直接关机了,作为调用者不关注服务方的具体细节 public void closeComputer() { cp.shutDown(); } } |
1.10 合成复用原则
Composite Reuse Principle,CRP
核心思想:尽量使用对象组合,而不是继承来达到复用的目的(组合优于继承), 继承关系是强耦合,组合关系是低耦合。
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
针对接口编程,而不是针对实现编程。
为了交互对象之间的松耦合设计而努力。
需求:制作一个集合,要求该集合能记录曾经添加过多少个元素。Java代码如下:
package com.txw.principle.compositereuse.v1;
import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * @Author:Adair */ @SuppressWarnings("all") // 注解警告信息 // 需求:制作一个集合,要求该集合能记录曾经添加过多少个元素。 class MySet extends HashSet { private int count = 0; @Override public boolean add(Object obj){ count++; return super.add(obj); } @Override public boolean addAll(Collection c) { count += c.size(); return super.addAll(c); } public int getCount(){ return count; } } @SuppressWarnings("all") // 注解警告信息 public class Client{ public static void main(String[] args) { MySet set = new MySet(); Set set2 = new HashSet(); set2.add("Java性能优化权威指南"); set2.add("架构之美"); set2.add("一线架构师实践指南"); set2.add("Thinking in Java"); set.addAll(set2); System.out.println(set.getCount()); // 为什么是8?因为addAl重写了add() } } |
修改代码如下:
package com.txw.principle.compositereuse.v2;
import java.util.HashSet; import java.util.Set; /** * @Author:Adair */ // 需求:制作一个集合,要求该集合能记录曾经添加过多少个元素。 //针对v1包的问题,addAll会回调add方法,我们修改代码如下:把addAll删除掉,不重写 //反正父类的addAll本身就会去调add @SuppressWarnings("all") // 注解警告信息 class MySet extends HashSet { private int count = 0; @Override public boolean add(Object obj){ count++; return super.add(obj); } public int getCount(){ return count; } } @SuppressWarnings("all") // 注解警告信息 public class Client{ public static void main(String[] args) { MySet set = new MySet(); Set set2 = new HashSet(); set2.add("Java性能优化权威指南"); set2.add("架构之美"); set2.add("一线架构师实践指南"); set2.add("Thinking in Java"); set.addAll(set2); System.out.println(set.getCount()); } } /* 看起来解决了? 还有问题吗? 问题:目前的代码必须依赖这样的事实:就是HashSet的addAll方法必须去回调add方法, 万一JDK版本升级,addAll不在回调add方法了,则在将来的版本中,我们自定义的这个Myset就将被 “撼动” HashMap jdk1.6 entrty 1.7 数据+ 链表 1.8 数据+ 链表 + 红黑树 node */ |
package com.txw.principle.compositereuse.v3;
import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * @Author:Adir */ // 需求:制作一个集合,要求该集合能记录曾经添加过多少个元素。 // 修改如下:自己重写addAll不再让count累加c.size(),而是保证addAll一定要用add() @SuppressWarnings("all") // 注解警告信息 class MySet extends HashSet { private int count = 0; @Override public boolean add(Object obj){ count++; return super.add(obj); } //不依赖脆弱的父类 @Override public boolean addAll(Collection c) { boolean bln = false; for(Object obj : c){ //只要集合里有一个元素添加成功,就返回true if (add(c)){ bln = true; } } return bln; } public int getCount(){ return count; } } @SuppressWarnings("all") // 注解警告信息 public class Client{ public static void main(String[] args) { MySet set = new MySet(); Set set2 = new HashSet(); set2.add("Java性能优化权威指南"); set2.add("架构之美"); set2.add("一线架构师实践指南"); set2.add("Thinking in Java"); set.addAll(set2); System.out.println(set.getCount()); } } /* 1. JDK更新 HashSet多了入口方法 addOne() --> m没有重写,始料未及 2. 重写了 Add()方法,addAll()方法,如果在HashSet中有方法依赖于这两个方法呢? 围绕 JDK变化 --》 HashSet */ |
package com.txw.principle.compositereuse.v4;
import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * @Author:Mars * @wx:10769582 */ // 需求:制作一个集合,要求该集合能记录曾经添加过多少个元素。 // 针对V3的两个问题,我们做出改动: //改动:1. MySet不继承HashSet,让MySet与HashSet发生关联关系(组合)。 @SuppressWarnings("all") // 注解警告信息 class MySet { private Set set = new HashSet(); private int count = 0; //入口只有两个 public boolean add(Object obj){ count++; return set.add(obj); } //不依赖脆弱的父类 public boolean addAll(Collection c) { count += c.size(); return set.addAll(c); } public int getCount(){ return count; } } @SuppressWarnings("all") // 注解警告信息 public class Client{ public static void main(String[] args) { MySet set = new MySet(); Set set2 = new HashSet(); set2.add("Java性能优化权威指南"); set2.add("架构之美"); set2.add("一线架构师实践指南"); set2.add("Thinking in Java"); set.addAll(set2); set.add("呼啸山庄"); System.out.println(set.getCount()); } } /** * 完美 * 疑惑: * 1. 难道以后都不用继承了吗? * 2. 难道以后都不使用方法重写了吗? * 原则: 如果父类作者与子类作者不是同一个人,就别继承 * 父类作者不知子类重写哪个方法 * 子类不知未来的父类会加入什么新方法 * 如果两者是同一个人,那就可以随便去使用继承 * 我们自己写代码,继承、重写、随便使用 * 如果我们仅仅只是为了复用代码,而去继承别人的类,难免出现问题 */ |
反例的代码如下:
package com.txw.principle.compositereuse.v4;
import java.util.Stack; /** * @Author:Adair */ //反面教材 @SuppressWarnings("all") // 注解警告信息 public class Client2 { public static void main(String[] args) { // 入栈出栈 FILO 先进后出,后进先出 Stack<String> stack = new Stack<>(); //入栈 stack.push("A"); stack.push("B"); stack.push("C"); stack.push("D"); //出栈 // System.out.println(stack.pop()); // System.out.println(stack.pop()); // System.out.println(stack.pop()); // System.out.println(stack.pop()); // 这是先进后出? 不是 System.out.println(stack.remove(1)); /* get remove导致Stack不再是FILO Stack extends Vector<E> 为了复用 remove get put直接导致了栈不是栈 不要随便拿JDK源码来当标杆,还会有很多问题。 */ } } |
1.11 思考
我们已经知道,dao层需要4样东西:dao接口 dao实现 模型 和dao工厂
那么思考一下:
1. dao层这样的设计方式是否符合依赖倒置原则?为什么?
答:符合依赖倒置原则,典型的业务层面向接口,以下各种都实现了接口,哪怕要换实现,上层是不会变的,上层依赖于接口,不需要理会下层实现变动。
2. dao层这样的设计方式是否符合开闭原则?为什么?
答:符合开闭原则, 保证以前的代码不变动,进行扩展就行了。
以上是关于源码分析之七大设计原则的主要内容,如果未能解决你的问题,请参考以下文章