设计模式六大原则

2019年2月26日19:41:21设计模式六大原则为什么会有六大原则有言曰,“无规矩不成方圆”,有“规”才能画“圆”,那设计模式要遵循的六大原则要画一个什么的“圆”呢?这里要从面向对象编程说起,从面向过程编程到面向对象编程是软件设计的一大步,封装、继承、多态是面向对象的三大特征,本来这些都是面向对象的好处,但是一旦有人滥用了,就有了坏味道。比如,封装是隐藏对象的属性和实现细节的,我想到了还没提倡...

设计模式六大原则

2019年2月26日19:41:21

设计模式六大原则

为什么会有六大原则

有言曰,“无规矩不成方圆”,有“规”才能画“圆”,那设计模式要遵循的六大原则要画一个什么的“圆”呢?

这里要从面向对象编程说起,从面向过程编程到面向对象编程是软件设计的一大步,封装、继承、多态是面向对象的三大特征,本来这些都是面向对象的好处,但是一旦有人滥用了,就有了坏味道。

比如,封装是隐藏对象的属性和实现细节的,我想到了还没提倡MVC的时候,一个servlet里的doGet、doPost方法就完成了所有事情,业务逻辑、数据持久化、页面渲染等,这样一来我们需要修改业务逻辑的时候是修改这个servlet,需要修改数据持久化的是修改这个servlet,甚至页面修改也是修改这个servlet。这样可维护性就很差了。

因为滥用或者不正确的时候导致代码的坏味道,导致系统的可维护性和复用性等变低,所以面向对象需要遵循一些原则make the code better。如:一个servlet干所有事情可以改为MVC,每一层的类做自己负责的事情,遵循单一职责原则。

为了提高系统的可维护性、复用性和高内聚低耦合等,所以有了六大原则。因为设计模式是面向对象实践出来的经验,所以这六大原则既是面向对象的六大原则,也是设计模式的六大原则。

六大原则

设计模式六大原则

先来个图,总体感受一下,其实说简单也简单,死记硬背这六个名词不用十分钟,但是要使用得游刃有余,还是要下一点功夫的。本文也只是纸上谈兵,聊聊六大原则的定义、用法、好处等。

单一职责原则(Single Responsibility Principle,SRP)

定义:不要存在多于一个导致类变化的原因(There should never be more than one reason for a class to change.)。

就像我前面说到的那个例子,一个servlet干完了所有事情,这样导致servlet变化的原因就不止一个了,所以要将这些事情分给不同的类。

比如我现在要实现一个登录的功能,servlet代码是这样的:

public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  // 1、获取前端传过来的数据  // 2、连接数据库,查询数据  // 3、比较数据,得出结果  // 4、封装结果,返回给前端 }}

应用MVC后,代码修改如下:

public class LoginController { private LoginService loginService; public ModelAndView Login(HttpServletRequest req, HttpServletResponse resp) {  // 1、获取前端传过来的数据  loginService.login();  // 4、封装结果,返回给前端  return null; }}public class LoginService { private UserDao userDao; public boolean login() {  userDao.findOne();  // 3、比较数据,得出结果  return false; }}public class UserDao { public User findOne(){  // 2、连接数据库,查询数据  return null; }}

有图如下:
单一职责原则

这样职责分明,有变更需求只需要找到职责相关的那部分修改就好。比如要修改比较逻辑,就修改Service层代码;要修改连接数据库,就修改Dao层就可以了;要修改返回页面的内容,就修改Controller层就可以了。

应用场景:在项目开始阶段要明确类的职责,如果发现一个类有两个或以上的职责,那就拆成多个类吧。如果是项目后期,要评估好修改的代价之后在重构。别让一个类做的事情太多。

好处:实现高内聚、低耦合,增加代码的复用性。

开闭原则(Open Closed Principle, OCP)

定义:软件实体,如:类、模块与函数,对于扩展开放,对修改关闭(Software entities like classes, modules and functions should be open for extension but closed for modifications.)。

从简单工厂模式到工厂方法模式,完美诠释了开闭原则的应用场景。有兴趣可以查看本人所写的《简单工厂模式》和《工厂方法模式》。

用类对象实现操作符运算:

简单工厂模式实现:

public static IOperation createOperation(String op) { IOperation operation = null; if (“ “.equals(op)) {  operation = new AddOperationImpl(); } else if (“-“.equals(op)) {  operation = new SubOperationImpl(); } else if (“*“.equals(op)) {  operation = new MulOperationImpl(); } else if (“/“.equals(op)) {  operation = new DivOperationImpl(); }  return operation;}

这是简单工厂模式中的工厂角色实现创建所有实例的内部逻辑的方法,调用方法时,根据传进来的操作符选择不同的实现类,但是如果我要添加一个乘方的话,就需要添加else if结构,没有对修改关闭,这样就不符合开闭原则了。

工厂方法模式实现:

// 加// 创建具体工厂IOperationFactory operationFactory = new AddOperationFactoryImpl();// 创建具体产品IOperation operation = operationFactory.createOperation();// 调用具体产品的功能int result = operation.getResult(a, b); 

需要什么运算,就继承IOperationFactory实现对应的实现类,使用时只需要在需要的地方new这个实现类即可。不用修改工厂类,增加运算就增加抽象工厂类的实现类,符合开闭原则。

应用场景:在系统的任何地方

好处:使得系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性

里氏替换原则(Liskov Substitution Principle,LSP)

定义:使用基类的指针或引用的函数,必须是在不知情的情况下,能够使用派生类的对象(Functions that use pointers or references to base classes must be able to use objects of derived classes whithout knowing it.)。

为什么叫里氏替换原则? 里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基�梅隆大学Jeannette Wing教授于1994年提出。

里氏替换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如,我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。

上面叙述转为代码如下:

// 动物public interface Animal { public String getName();}// 狗public class Dog implements Animal{ private String name = “狗“; @Override public String getName() {  return this.name; }}// 老鼠public class Mouse implements Animal{ private String name = “老鼠“; @Override public String getName() {  return this.name; }}// 测试类public class ISPTest { public static void main(String[] args) {  Animal dog = new Dog();  Animal mouse = new Mouse();  iLoveAnimal(dog);  iLoveAnimal(mouse);//  iLoveDog(dog);//  iLoveDog(mouse); } public static void iLoveAnimal(Animal animal) {  System.out.println(animal.getName()); } public static void iLoveDog(Dog dog) {  System.out.println(dog.getName()); }}

其中iLoveAnimal(Animal animal)可以传递的对象参数有Dog和Mouse,因为Dog和Mouse是Animal的子类,所以编译通过。iLoveDog(Dog dog)不能Mouse为参数,虽然他们同属Animal的子类,编译不能通过。在编译阶段,Java编译器会检查一个程序是否符合里氏替换原则,但是Java编译器的检查是有局限的,这只是一个与实现无关、纯语法意义上的检查,在设计上我们要注意遵循里氏替换原则。

理论上,里氏替换原则是实现开闭原则的重要方式之一,使用基类对象的地方都可以使用子类对象,所以在程序中尽量使用基类类型来对对象进行定义,而在运行时在确定其子类类型,当子类类型改变时,就能实现扩展,并没有对现有的代码结构进行修改

实践中,我们能做的是:

  • 尽量将基类设计为抽象类和接口

  • 子类必须实现父类中声明的所有方法,且子类的所有方法必须在基类中声明

最少知识原则(Least Knowledge Principle,LKP)又名迪米特法则(Law of Demeter)

定义:只与你最直接的朋友交流(Only talk to you immediate friends.)。

又名迪米特法则的原因是:迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目。

根据迪米特法则有,对象O的一个方法M仅能访问以下类型的对象:

  • 1、当前对象O自身(this)

  • 2、M方法的参数对象(如,toString(Integer i)中对象i

  • 3、当前对象O成员对象(当前对象O直接依赖的对象)

  • 4、M方法中所创建的对象

重要的是,方法M不应该调用这些方法返回对象的方法,就是链式调用返回的但返回的并不是自身对象的对象的方法。和你朋友说话,而不是和你朋友的朋友,对于你来说是陌生人的人说话。

下面是一个例子:

public class LawOfDelimterDemo { /**  * 这个方法有两个违反最少知识原则或迪米特法则的地方。  */ public void process(Order o) {  // 这个方法调用符合迪米特法则,因为o是process方法的参数,是类型2的参数  Message msg = o.getMessage();  // 这个方法调用违反了迪米特法则,因为使用了msg对象,这个对象是从参数对象中得到的对象。  // 我们应该让Order去normalize这个Message,例如:o.normalizeMessage(),而不是使用msg对象的方法  msg.normalize();  // 这也是违反迪米特法则的,使用了方法链代替上面说的msg临时变量。  o.getMessage().normalize();  // 构造函数调用  Instrument symbol = new Instrument();  // as per rule 4, this method call is OK, because instance of Instrument is created locally.  // 这个方法调用是符合迪米特法则的,因为Instrument实例是本地创建的,就是类型4的对象,process方法中所创建的对象  symbol.populate();  }}

好处:降低系统的耦合性,增加系统的可维护性和适应性。因为较少依赖于其他对象的内部结构,其他对象的修改就重新修改它们的调用者。

坏处:可能会增加对象的方法,引发其他bug。

接口隔离原则(Interface Segregation Principle, ISP)

定义:一个类与另外一个类之间的依赖性,应该依赖于尽可能小的接口(The dependency of one class to another one should depend on the smallest possible interface.)。

例子:首先有一个经理,负责管理工人。其次,有两种类型的工人,一种是在平均水平的工人,一种是高效率的工人,这些工人都需要午休时间来吃饭。最后还有一种机器人在工作,但是机器人不需要午休。

设计实现代码:

interface IWorker { public void work(); public void eat();}// 一般工人class Worker implements IWorker { public void work() {  // 正常工作 } pubic void eat() {  // 午休吃饭 }}// 高效率工人class SuperWorker implements IWorker { public void work() {  // 高效率工作 } public 
源文地址:https://www.guoxiongfei.cn/cntech/12930.html
0