设计模式—六大设计原则
本文开始对《设计模式之禅》进行学习总结,首先是六大设计原则。
单一职责原则
单一职责原则(Single Responsibility Principle)简称SRP,这个原则存在的争议之处是“职责”的定义、划分。
先举例说明什么事单一职责:以用户管理、修改用户的信息、增加机构(一个人属于多个机构)、增加角色等维护,用户有这么多的行为维护。
假如我们把他们写到一个接口中,类图如图最左侧,从图中可以很容易看出,类的定义没有划分用户的属性和用户的行为,按照抽取属性和行为的思路可以进行职责划分。
如图中间,职责划分后将接口拆为两个
IUserBo
和IUserBiz
,现在是面向接口编程,所以产生UserInfo
对象后可以既把它当做IUserBo
接口获得用户属性,也可以当做IUserBiz
操作维护用户相关信息。.... IUserInfo userInfo = new UserInfo(); // 赋值 IUserBO userBO = (IUserBo) userInfo; userBO.setPassword("abc"); // 执行动作 IUserBiz userBiz = (IUserBiz) userInfo; userBiz.deleteUser(); ....
项目实际习惯更倾向于右侧使用两个不同的类或者接口进行操作。
SRP的原话解释是:There should never be more than one reason for a class to change
这句话很好看懂,但是看懂是一回事,实践又是另一回事了。举例打电话:电话通话有4个过程:拨号、通话、回应。类图如下
public interface IPhone{// 拨通电话public void dial(String phoneNumber);// 通话public void chat(Object o);// 通话完毕,挂电话public void hangup();
}
实现类比较简单,这个接口起始已经接近完美,看清楚是”接近“!一个接口或类只有一个原因引起变化,也就是一个接口只负责一件事,但是上面的接口只负责一件事吗?只有一个原因引起变化吗?好像不是!
这个接口包含了两个职责:一个是协议管理(dial
和hanup
),另一个是数据传输(chat
),所以考虑职责的划分和互不影响,拆分为两个接口如下图左侧:
但是图中左侧的设计,一个手机要把两个类组合才能使用,这种组合是一种强耦合关系,并且增加了两个类。你和我都有共同的生命周期,这种强耦合的组合方式还不如使用接口的实现方式,如图右侧,这才是完美的设计!
你可能会觉得这个IPhone
有两个原因引起变化了呀!但是别忘了我们是面向接口编程,对外提供的是接口而不是实现类,而且要是如果真要实现类的单一职责,就必须使用上面的组合模式,这会引起类间的耦合过重、类的数量增加等问题。
通过上面的例子,总结一下单一职责原则有什么好处:
- 类的复杂性降低,实现什么职责有清晰明确的定义;
- 可读性提高,复杂性降低,当然可读性提高了;
- 可维护性提高,复杂性降低当然更容易维护;
- 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做的好,一个接口修改只对相应的实现类有影响,对其他接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
反思:
看过电话的例子后是不是陷入了深思?不要怀疑自己的能力,单一职责最难划分的就是职责。一个职责一个接口,但是”职责“没有一个量化标准。从功能上说IPhone
一个接口,一个实现类没有错,实现了电话的功能而且设计很简单,实际的项目我想大家都会这么设计。项目要考虑可变因素和不可变因素,以及相关的收益成本比。
一个方法承担多个职责,下图仅仅是个简单的逻辑尽管按左边的编写可能后续维护人员也可以看懂,但是如果是非常复杂的类作为参数,那么维护起来会浪费很多时间。方法职责不清晰,不单一,不要让别人猜测这个方法可能是用来处理什么逻辑的。比如图中右边每个方法的职责非常明确,不仅开发简单而且日后维护也非常容易。
里氏替换原则
里氏替换原则(Liskov Substitution Principle),面向对象的语言中,继承是必不可少的。继承在带来代码共享、提高扩展性的同时,带来的是代码的侵入、降低灵活、增加耦合,没有规范的继承会使得代码难以维护。
里氏替换原则为良好的继承定义了一个规范,包含一下4层含义:
1、子类必须完全实现父类的方法
举一个士兵拿枪射击的例子:
士兵具有枪,可以设置拿什么枪,并且有杀敌动作。枪是一个抽象类,可以有很多个实现如手枪、步枪、机枪。
枪支抽象类
public abstract class AbstractGun{// 枪用来干什么public abstract void shoot();
}
枪支实现类
public class HandGun extends AbstractGun{@Overridepublic void shoot(){System.out.println("手枪射击...");}
}public class Rifle extends AbstractGun{@Overridepublic void shoot(){System.out.println("步枪射击...");}
}public class MachineGun extends AbstractGun{@Overridepublic void shoot(){System.out.println("机枪射击...");}
}
士兵的实现类
public class Soldier{private AbstractGun gun;public void setGun(AbstractGun gun){this.gun = gun;}public void killEnemy(){System.out.println("士兵开始射杀敌人...");gun.shoot();}
}
在这个程序中给士兵什么武器将用什么射击,编写程序时候士兵根本不知道是哪个型号的枪(子类)被传入。
【注意】在类中调用其他类时务必使用父类或接口,如果不能则说明类的设计已经违背里氏替换原则
从上例子中再想一想如果我们有一个玩具手枪,该如何定义?是否是直接继承抽象枪类?
答案是否定的,因为玩具枪不能用来杀人,这个不应写在shoot()
方法中。那么如何解决呢?
如果已经将玩具枪继承自
AbstractGun
那么就需要再Soldier
中增加instanceof的判断,如果是玩具枪就不能用来杀人。这是一种方案,但是如果你的产品出现问题,因为修正了一个这样的Bug就要求所有与这个父类有关系的类都增加一个判断,这显然是不可行的。ToyGun脱离继承,建立一个独立的父类,为了实现代码复用,可以与
AbstractGun
建立关联委托关系,将声音、形状等委托给其处理,如图
2、子类可以有自己的个性
子类可以有自己的方法和属性,里氏替换原则可以正着用,但不能反过来用,比如子类出现的地方,父类未必可以胜任。再举例狙击手类图如下:
很简单,AUG继承了Rifle类,狙击手(Snipper)则直接使用AUG狙击步枪
public class Aug extends Rifle {// 瞄准public void zoomOut() {System.out.println("通过望远镜查看敌人...");}public void shoot() {System.out.println("AUG射击...");}
}
这样狙击手设计杀人的实现如下:
public class Client {public static void main(String[] args) {Snipper ab = new Snipper();ab.setRifle(new Aug());// 错误代码// ab.setRifle((Aug) new Rifle());ab.killEnemy();}
}
这样狙击手就可以先瞄准,再射击了。看一下错误代码,显然是不行的,会在运行时抛出java.lang.ClassCastException
异常,这也是大家经常说的向下转型(downcast)是不安全的,从里氏替换原则来看,就是有子类可以实现的地方父类未必可以胜任。
3、重写或实现父类的方法时输入参数可以被放大
这句话是什么意思呢?举例说明你就会明白
// 父类
public class Father {public Collection doSomething(HashMap map) {System.out.println("父类被执行...");return map.Values();}
}
// 子类
public class Son extends Father {public Collection doSomething(Map map) {System.out.println("子类被执行...");return map.Values();}
}
注意这里不是@Override,因为是重载。实例化父类和子类执行doSomething
方法的结果都是”父类被执行…“,这是正确的,如果你想让子类的方法运行,就必须重写父类的方法。这就是里氏替换原则第二条,父类出现的地方子类可以胜任。
但是如下例:
// 父类
public class Father {public Collection doSomething(Map map) {System.out.println("父类被执行...");return map.Values();}
}
// 子类
public class Son extends Father {public Collection doSomething(HashMap map) {System.out.println("子类被执行...");return map.Values();}
}
将doSomething
方法参数互换后,实例化父类得到的结果是”父类被执行…“,而实例化子类执行后发现是”子类被执行…“,完蛋了吧!子类没有重写父类的前提下,子类方法被执行了!
这样就会引起业务逻辑的混乱,因为再实际应用中父亲一般都是抽象类,子类是实现类,你传递一个实现类就会”歪曲“父类的意图,引起意想不到的业务逻辑混乱。所以子类中的前置条件必须与超类中被覆写的方法前置条件相同或更宽松。
4、重写或实现父类的方法时输出结果可以被缩小
父类的一个方法返回值为T,子类的相同方法(重载或重写)的返回值为S,那么里氏替换原则要求S必须小于等于T,也就是说要么S和T是同一个类型,要么S是T的子类。
实践
在项目中采用里氏替换原则尽量避免子类的”个性“,一旦有”个性“,子类和父类的关系就很难调和了,把子类当做单独业务来使用,父子关系就会变得扑朔迷离。
依赖倒置原则
依赖倒置原则(Dependence Inversion Principle, DIP),原始定义包含三层含义:
- 高层模块不应依赖低层模块,两者都应依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
不可分割的原子逻辑就是低层模块,再组装就是高层模块。
那什么是抽象和细节?Java中抽象就是接口或抽象类,两者都是不能直接被实例化的。而细节就是其实现类,可以直接被实例化。
什么是“倒置”?
先讲讲“正置”,依赖正置就是实现类间的依赖,也就是面向实现编程,我要开奔驰车就依赖奔驰车,使用笔记本电脑就依赖笔记本电脑。而编写程序就是对现实世界的事物进行抽象,抽象的结果就是有了接口和抽象类,然后我们根据设计需要产生了抽象间的依赖,代替了传统思维中事物间的依赖,“倒置”就是这样产生的。
依赖倒置原则再Java语言中的表现就是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
- 接口或抽象类不依赖于实现类;
- 实现类依赖接口或抽象类
总结起来就是“面向接口编程”
通过司机开奔驰车的例子来看一下,如果不适用依赖倒置会有什么问题
public class Client {public static void main(String[] args) {Driver zhangsan = new Driver();Benz benz = new Benz();zhangsan.driver(benz);}
}class Driver {public void driver(Benz benz) {benz.run();}
}class Benz {public void run() {System.out.println("奔驰汽车开始运行...");}
}
通过以上代码可以看到,司机张三开奔驰车没有任何问题,但是“变更才显真功夫”,业务需求的变更是永无休止的,在发生改变的时候才能发现我们的业务是否是松耦合。假如还有宝马车,张三该怎么办呢?
class BMW {public void run() {System.out.println("宝马汽车开始运行...");}
}
这里只增加了一个车类就不得不修改司机类才能使其运行!
在这个并行开发的时代,假如甲负责汽车的建造,乙负责司机类的建造,甲没有完成的情况下,乙不能完全的编写代码,这种情况下依赖倒置就显示出其威力了。
public class Client {public static void main(String[] args) {IDriver zhangsan = new Driver();ICar benz = new Benz();ICar bmw = new BMW();zhangsan.driver(benz);zhangsan.driver(bmw);}
}class Driver implements IDriver{@Overridepublic void driver(ICar car) {car.run();}
}class Benz implements ICar{@Overridepublic void run() {System.out.println("奔驰汽车开始运行...");}
}class BMW implements ICar{@Overridepublic void run() {System.out.println("宝马汽车开始运行...");}
}interface ICar{public void run();
}interface IDriver{public void driver(ICar car);
}
Client属于高层业务逻辑,它对低层模块的依赖都建立在抽象上。这样在新增低层模块(汽车类)时,只修改业务场景,对其他低层模块(Driver)不需要任何修改,把“变更”引起的风险降到最低。
依赖的三种写法
- 构造器传递依赖
- Setter方法传递依赖
- 接口注入(上例使用)
实践
- 每个类尽量都有接口或抽象类,或者两者都具备
- 任何类都不应该从具体类派生
- 尽量不要覆写基类的方法
- 结合里氏替换原则使用(父类能出现的地方,子类就能出现)
接口隔离原则
接口隔离原则(Interface Segregation Principle, ISP),客户端不应该依赖它不需要的接口,类间的依赖关系应建立在最小的接口上。
我们可以把定义概括为:建立单一接口,接口中的方法尽量少。看到这里,大家可能有疑惑了这和单一职责原则不是一样的吗?错,单一职责原则关注的是职责,是业务逻辑的划分,而接口隔离原则要求接口的方法尽量少。
一个接口有10个方法,在系统外通过文档约束“不使用的方法不要访问”,按照单一职责原则这是允许的,按照接口隔离原则是不允许的。因为后者要求用多个专门的接口来建立不同的方法。
举例星探找美女来看看这个原则到底对我们提出了什么要求,首先定义一下美女,要同时具备:面貌、身材和气质,如下类图体现一下星探找美女的过程。
按照这个规范星探(Searcher)和美女(PettyGirl)的定义就非常容易了,以下通过三个方法把对美女的要求都定义了。
public interface IPettyGirl {// 好面貌public void goodLooking();// 好身材public void niceFigure();// 有气质public void greatTemperament();
}public class PrettyGirl implements IPettyGirl {private String name;public PrettyGirl(String name) {this.name = name;}@Overridepublic void goodLooking() {System.out.println(this.name + "-----脸蛋很漂亮!");}@Overridepublic void niceFigure() {System.out.println(this.name + "-----身材非常棒!");}@Overridepublic void greatTemperament() {System.out.println(this.name + "-----气质非常好!");}
}
接下来编写星探的代码
public abstract class AbstractSearcher {protected IPettyGirl prettyGirl;public AbstractSearcher(IPettyGirl prettyGirl) {this.prettyGirl = prettyGirl;}public abstract void show();
}public class Searcher extends AbstractSearcher {public Searcher(IPettyGirl prettyGirl) {super(prettyGirl);}@Overridepublic void show() {System.out.println("----------美女信息如下:----------");// 展示面容super.prettyGirl.goodLooking();// 展示身材super.prettyGirl.niceFigure();// 展示气质super.prettyGirl.greatTemperament();}
}
测试场景类将角色串联起来
public class Client {public static void main(String[] args) {IPettyGirl rose = new PrettyGirl("肉丝");AbstractSearcher searcher = new Searcher(rose);searcher.show();}
}
结果
----------美女信息如下:----------
肉丝-----脸蛋很漂亮!
肉丝-----身材非常棒!
肉丝-----气质非常好!
星探寻找美女的程序开发完毕了,回头思考一下IPettyGirl
这个接口,是否做到了最优化设计?
答案是没有。我们的审美观点一直在变,杨贵妃虽然胖,但是不影响入选中国四大美女,说明审美观与时代是有关系的。当你发现一个女孩身材、面貌一般,但是气质非常好,我相信大部分人会把这样的女孩叫做美女,这就产生了气质型美女。但是我们的接口去定义了美女必须是三者都具备,按照这个标准,气质类型美女就不能算作美女。分析到这里我们发现IPettyGirl
的设计是有缺陷,过于庞大了,根据接口隔离原则,星探应该依赖于有部分特质的女孩子,而我们把这些特质都封装到了一起!
问题找到了,重新设计一下,修改后的类图如下所示。
修改后星探寻找最标准的美女,需要实现两个接口就可以了。通过这样的重构后不管以后是要气质美女还是外形美女,都可以保持接口的稳定。以上把一个臃肿的接口变更为两个独立的接口就是接口隔离原则。
思考
如之前单一职责原则中打电话的例子,当时将业务拆分为通信连接接口和数据传输接口,考虑一下通信连接接口是否能继续拆分下去。挂电话有两种可能,一个是正常挂断,一个是断电挂断(中继服务器检查通知停止计费),思考到这里是不是动手要把这个通信连接接口拆分为一个负责连接一个负责挂电话?且慢!如果拆分了就不符合单一职责原则了,因为从业务逻辑上讲,通信的建立和关闭已经是最小的业务逻辑了,再细分下去就是对业务或者协议(其他业务)拆分了。看到这里发现一个原则让拆,另一个不让拆怎么办呢?
【注意】接口隔离原则拆分接口时候首先必须满足单一职责原则。
实践
- 一个接口只服务于一个子模块或业务逻辑
- 通过业务逻辑压缩接口中的public方法,时长回顾,给接口”减肥增肌“
- 已经被污染了的接口尽量去修改,若变更的风险较大,采用适配器模式进行转化处理
- 了解业务,拒绝盲从
迪米特法则
迪米特法则(Law of Demeter,LoD)也成为最少知识原则(Least Knowledge Principle,LKP),一个对象应该对其他对象有最少的了解,通俗的讲就是一个类对自己需要耦合和调用的类知道的最少,我就知道你有这么多public方法,你内部如何复杂跟我都没有关系。
举例:老师让体育委员确认一下全班女生来齐了没有。
public class MyTest {public static void main(String[] args) {Teacher teacher = new Teacher();// 老师发命令teacher.command(new GroupLeader());}
}class Teacher {public void command(GroupLeader groupLeader) {List<Girl> girls = new ArrayList();for (int i = 0; i < 20; i++) {//初始化girls.add(new Girl());}// 告诉体委开始清点groupLeader.countGirls(girls);}
}class GroupLeader {public void countGirls(List<Girl> girls) {System.out.println("女生的数量是:" + girls.size());}
}class Girl {}
类图和代码:
体育委员根据老师要求对女生进行清点,并得出数量。回头思考一下有什么问题,首先确认Teacher类有几个朋友类,仅有GroupLeader。为什么Teacher对Girl产生依赖,而Girl不是朋友类?因为朋友类的定义是出现在成员变量、方法和输入输出参数中的类。迪米特法则告诉我们一个类只和朋友类交流,但是command方法却与Girl类有了交流。这就发现了问题,将类图修改。
修改后的场景代码:
public class MyTest {public static void main(String[] args) {Teacher teacher = new Teacher();List<Girl> girls = new ArrayList<>();for (int i = 0; i < 20; i++) {girls.add(new Girl());}// 老师发命令teacher.command(new GroupLeader(girls));}
}class Teacher {public void command(GroupLeader groupLeader) {// 告诉体委开始清点groupLeader.countGirls();}
}class GroupLeader {private List<Girl> girls;public GroupLeader(List<Girl> girls) {this.girls = girls;}public void countGirls() {System.out.println("女生的数量是:" + girls.size());}
}class Girl {}
对程序进行了简单的修改,将Teacher对Girl的依赖移动到了场景类中。降低了系统的耦合,提高了健壮性。
再举一个安装软件的例子:我们安装软件的时候经常有一个导向动作,第一步确认安装,第二步确认License,再选安装目录…这是一个典型的顺序执行过程,运用到程序中就是调用一个或者多个类。
public class MyTest {public static void main(String[] args) {InstallSoftWare invoker = new InstallSoftWare();invoker.installWizard(new Wizard());}
}class InstallSoftWare {public void installWizard(Wizard wizard) {int first = wizard.first();// 根据first的结果看是否执行下面if (first > 50) {int second = wizard.second();if (second > 50) {int third = wizard.third();if (third > 50) {wizard.first();}}}}
}class Wizard {private Random rand = new Random(System.currentTimeMillis());public int first() {System.out.println("第一步...");return rand.nextInt(100);}public int second() {System.out.println("第二步...");return rand.nextInt(100);}public int third() {System.out.println("第三步...");return rand.nextInt(100);}
}
以上程序虽然简单但是结果和随机数有关,每次结果都不相同。思考一下Wizard把太多方法都暴露给InstallWizard类了,两者的关系太密切,耦合变得非常牢固。如果要改变Wizard类的first方法的返回值类型为boolean,就需要修改InstallWizard类!
public class MyTest {public static void main(String[] args) {InstallSoftWare invoker = new InstallSoftWare();invoker.installWizard(new Wizard());}
}class InstallSoftWare {public void installWizard(Wizard wizard) {// 直接调用wizard.installWizard();}
}class Wizard {private Random rand = new Random(System.currentTimeMillis());private int first() {System.out.println("第一步...");return rand.nextInt(100);}private int second() {System.out.println("第二步...");return rand.nextInt(100);}private int third() {System.out.println("第三步...");return rand.nextInt(100);}public void installWizard() {int first = this.first();// 根据first的结果看是否执行下面if (first > 50) {int second = this.second();if (second > 50) {int third = this.third();if (third > 50) {this.first();}}}}
}
总结:核心观念就是类间解耦,弱耦合。
只和朋友交流
朋友间也是有距离的
是自己的就是自己的
如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类
谨慎使用Serializable
比如客户端修改了访问权限从private-》public,服务端没有修改就会报序列化失败
开闭原则
核心概念:对扩展开放,对修改关闭
直接举例子:以书店售书为例,类图如下
IBook定义了三个属性:名称、价格、作者。BookStore是书店,小说类NovelBook是一个具体实现类
class BookStore {private final static List<IBook> bookList = new ArrayList<>();// 代替持久化数据static {bookList.add(new NovelBook("天龙八部", 3200, "金庸"));bookList.add(new NovelBook("悲惨世界", 3500, "雨果"));bookList.add(new NovelBook("巴黎圣母院", 4300, "雨果"));}public static void main(String[] args) {NumberFormat format = NumberFormat.getInstance();format.setMaximumFractionDigits(2);format.setMinimumFractionDigits(2);System.out.println("---------------书店卖出书籍记录如下:---------------");for (IBook book : bookList) {System.out.println("书籍名称:" + book.getName() + "\t书籍作者:" + book.getAuthor()+ "\t书籍价格:¥" + format.format(book.getPrice() / 100.0) + "元");}}
}class NovelBook implements IBook {private String name;private int price;private String author;public NovelBook(String name, int price, String author) {this.name = name;this.price = price;this.author = author;}@Overridepublic String getName() {return this.name;}@Overridepublic int getPrice() {return this.price;}@Overridepublic String getAuthor() {return this.author;}
}
结果如下:
---------------书店卖出书籍记录如下:---------------
书籍名称:天龙八部 书籍作者:金庸 书籍价格:¥32.00元
书籍名称:悲惨世界 书籍作者:雨果 书籍价格:¥35.00元
书籍名称:巴黎圣母院 书籍作者:雨果 书籍价格:¥43.00元
现在项目投产了,书籍正常销售,书店为了回馈顾客,所有40元以上的书籍9折销售,其他8折销售,对于已经投产的项目,这就是一个变化,如何应对这个变化呢?
- 修改接口:IBook上新增一个方法getOffPrice()专门进行打折处理,所有实现类都实现该方法。但是后果就是实现类要修改,main方法也要修改,接口IBook作为接口应该是稳定的且可靠的,不应该经常变化,否则作为契约的作用就失效了。因此否定该方案。
- 修改实现类:修改NovelBook类中的方法直接进行打折处理,项目中也经常使用该方法,通过替换class文件的方式可以完成部分业务的变化(或者缺陷修复)但是该方案使得采购人员看不到实际的变化,会因信息不对称决策失误,因此该方案也不是最优方案
- 通过扩展实现变化:增加一个子类OffNovelBook,覆写getPrice方法,高层次模块(static静态块)通过OffNovelBook类产生新的对象,完成业务变化对系统的最小化开发。修改少,风险也小。
class BookStore {private final static List<IBook> bookList = new ArrayList<>();// 代替持久化数据static {bookList.add(new OffNovelBook("天龙八部", 3200, "金庸"));bookList.add(new OffNovelBook("悲惨世界", 3500, "雨果"));bookList.add(new OffNovelBook("巴黎圣母院", 4300, "雨果"));}public static void main(String[] args) {NumberFormat format = NumberFormat.getInstance();format.setMaximumFractionDigits(2);format.setMinimumFractionDigits(2);System.out.println("---------------书店卖出书籍记录如下:---------------");for (IBook book : bookList) {System.out.println("书籍名称:" + book.getName() + "\t书籍作者:" + book.getAuthor()+ "\t书籍价格:¥" + format.format(book.getPrice() / 100.0) + "元");}}
}class OffNovelBook extends NovelBook {public OffNovelBook(String name, int price, String author) {super(name, price, author);}@Overridepublic int getPrice() {int selfPrice = super.getPrice();int offPrice = 0;if (selfPrice > 4000) {offPrice = selfPrice * 90 / 100;} else {offPrice = selfPrice * 80 / 100;}return offPrice;}
}
由于静态代码块属于高层次模块(持久化)业务变更确实会造成高层次模块的或多或少改变,改变和风险尽量都要小。
【注意】开闭原则扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有高层模块的耦合,否则就是一个孤立无意义的代码片段。
可以归纳变化为以下三种类型:
逻辑变化
只变化一个逻辑,如axb+c变化为axbxc,通过修改原有类的方法,前提是所有依赖的类都按照相同的逻辑处理
子模块变化
一个模块变化影响其他模块,低层次引起高层次变化
视图变化
提供给客户使用的界面,假如是展示列表突然要增加一列,这一列要横跨N张表。这就要看我们原有的设计是否灵活,但还是可以通过扩展来完成变化。
开闭原则是一个比较虚的原则,前面五个原则是对开闭原则的具体解释,它像“好好学习天天向上”这样的口号一样,让我们怎么做,但是没有告诉我们具体怎么做,那如何应用到实际中呢?
抽象约束
比如售书例子,不光可以有小说实现类,还可以有计算机书籍实现类,但是计算机又有独特的属性:领域,那就增加一个接口继承顶层IBook接口,来约束计算机书籍的特有属性
元数据控制模块行为
比如验证登录的白名单约定逻辑
制定项目章程
项目开发过程,研发人员默认遵守的约定,比如所有Bean都自动注入等等
封装变化
相同的变化封装到相同的接口,不应该有两个不同的变化出现在同一个接口。
设计模式—六大设计原则相关推荐
- 设计模式-六大设计原则(附加实际开发建议以及计算器例子)
使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性.设计模式使代码编制真正工程化,是软件工程的基石脉络,如同大厦的结构一样. 文章结构: 1.单一职责原则(SRP): 2.里氏替换原 ...
- JAVA关于设计模式六大设计原则
设计模式 前言 一.单一职责原则 1.定义 2. 问题由来 3.单一职责优点: 二.里式替换原则 1.定义: 2.含义: 3.优缺点: 4.注意事项 三.依赖倒置原则 1.定义 2.作用 3.实现方法 ...
- Java设计模式六大设计原则
从今年的七月份开始学习设计模式到9月底,设计模式全部学完了,在学习期间,总共过了两篇:第一篇看完设计模式后,感觉只是脑子里面有印象但无法言语.于是决定在看一篇,到9月份第二篇设计模式总于看完了,这一篇 ...
- java设计模式-六大设计原则
1. .单一职责 一个类只负责一项职责. 2. 里氏替换原则 子类可以扩展父类的功能,但不能改变父类原有的功能.它包含以下4层含义: 子类可以实现父类 ...
- 设计模式六大设计原则 详细整理版
一.单一职责原则 定义: 一个接口,类或者方法只有一个原因引起变化.即只有一个职责. 解读: 职能多样是一个诱惑,一个方法如果能根据入参(可变参数等)或其他判断条件实现不同或多种功能,看起来很强大而且 ...
- 设计模式 - 六大设计原则之SRP(单一职责)
文章目录 概述 Case Bad Impl Better Impl 1. 定义接口 2. 职责分离-多种实现类 3. 单元测试 小结 概述 单一职责原则(Single Responsibility P ...
- 设计模式 - 六大设计原则之OCP(开闭原则)
文章目录 概述 Case 接口定义 接口实现 Bad Impl Better Impl 概述 Open-Close Principle 在面向对象编程领域中,开闭原则规定软件中的类.对象.模块和函数对 ...
- 设计模式 - 六大设计原则之LoD(迪米特法则原则)
文章目录 概述 Case 学生类 老师类 Bad Impl Better Impl 老师类改造 调用方:校长类改造 概述 迪米特法(Law Of Demeter , LoD)则又叫最少知道原则(Lea ...
- IOS设计模式的六大设计原则之开放-关闭原则(OCP,Open-Close Principle)
定义 一个软件实体(如类.模块.函数)应当对扩展开放,对修改关闭. 定义解读 在项目开发的时候,都不能指望需求是确定不变化的,大部分情况下,需求是变化的.那么如何应对需求变化的情况?这就是开放-关闭原 ...
最新文章
- 哪些人适合学习web前端?
- vue学习之路.02
- 2021年中国家装行业数字化转型研究报告
- 【语义分割】全卷积网络(Fully Convolutional Networks, FCN)详解
- 解决 pathForResource 返回 nil的问题
- Leetcode每日一题:10.09.sorted-matrix-search-lcci(排序矩阵查找)
- shaderop的定义
- 1074. Reversing Linked List (25)-PAT甲级真题
- 一大波干货学习资源分享
- 软件测试主要种类大全
- Java web项目目录结构以及作用详解
- 一个牧场目前一共有20头刚出生的羊,母羊、公羊各一半。假如母羊5岁时后每年生一胎(母羊,公羊各一半)。羊活到10岁后死亡。请问20年后这个牧场有多少只羊? 请用C#写出算法。
- php disable hugepage,禁用Transparent Huge Pages
- 此实现不是windows平台fips验证的加密算法的一部分
- erewrwerwer
- 利用matplotlib实现TMDB电影数据可视化
- 开发微信公众平台的基本功能
- 2022.3.2复盘
- 网站日志分析(二)——利用Quick BI制作企业化报表分析
- TensorFlow学习笔记(一)---graph,op,tensor,Session,Variables,Tensorboard