一、设计六大原则
六大原则分别是单一职责原则(Single Responsibility Principle,或者 SRP)、里氏替换原则(Liskov Substitution Principle , 或者 LSP)、依赖倒置原则(Dependence Inversion Principle ,或者DIP)、接口隔离原则(Interface Segregation Principle,或者ISP)、迪米特法则(Law Of Demeter,或者 LoD)、开闭原则(Open Close Principle、或者OCP)。
二、如何遵守
对于软件设计在遵循六大原则需要平衡,而不是是否问题(必须遵守),任何事情都是过犹不及,所以对于实际情况需要灵活运用,控制在一个合理的范围之内,就是一个不错的设计,如下图所示
下面列出几种实际项目中常见的几种可能,如下图所示
三、分别举例说明
1. 单一职责原则(Single Responsibility Principle,或者 SRP)
定义:一个类对应一项职责(避免修改一个多职能冗杂类,而是对单一类部分修改来的简单)
举例:动物园原来只有马牛羊,做一个标记吃草动物属性系统
class Animal{@b@ public static void mark(String animal_name){@b@ System.out.println(animal_name+"是素食动物");@b@ }@b@}@b@public class Test{@b@ public static void main(String[] args){@b@ Animal.mark("马");@b@ Animal.mark("牛");@b@ Animal.mark("羊");@b@ }@b@}
结果是
马是素食动物
牛是素食动物
羊是素食动物
举例:动物园新进肉食动物老虎,需扩展原标记系统
class VegAnimal{@b@ public static void mark(String animal_name){@b@ System.out.println(animal_name+"是素食动物");@b@ }@b@}@b@class CarAnimal{@b@ public static void mark(String animal_name){@b@ System.out.println(animal_name+"是肉食动物");@b@ }@b@}@b@public class Test{@b@ public static void main(String[] args){@b@ VegAnimal.mark("马");@b@ VegAnimal.mark("牛");@b@ VegAnimal.mark("羊");@b@ CarAnimal.mark("老虎");@b@ }@b@}
结果是
马是素食动物
牛是素食动物
羊是素食动物
老虎是肉食动物
如只修改原有方法判断逻辑,后期的三次、四次修改会造成Animal的mark标记方法异常的复杂,不利于后期的扩展延伸。该原则降低逻辑复杂度;增加类可读性,提高系统可维护性;降低系统各个模块功能间影响性。
2. 里氏替换原则(Liskov Substitution Principle)
定义:子类可以扩展父类的功能,但不能改变父类原有的功能(任何基类可以出现的地方,子类一定可以出现)
举例:设计一个BaseMath数学类计算两个数之和
public class BaseMath{@b@@b@ public static int funAdd(int a,int b){@b@ return a+b;@b@ }@b@}
现在要扩展数学类方法,使它满足先加再减50的计算
public class ExtMath extends BaseMath{@b@ @b@ public static int funAll(int a,int b){@b@ return funAdd(a,b)-50;@b@ }@b@}
对于上面二次需求我们继承扩展了基类方法,而不是重构funAdd方法,避免其他模块基于原来funAdd方法逻辑实现的重构
3. 依赖倒置原则(Dependence Inversion Principle)
定义:面向接口编程,上层和底层模块都应该依赖抽象定义(接口或抽象类),细节依赖抽象
举例:完成爸爸给儿子教育(读书、读报纸及各种杂志)
interface IReader{@b@ public String getContent();@b@}@b@@b@class Newspaper implements IReader {@b@ public String getContent(){@b@ return "xxx新开游乐场……";@b@ }@b@}@b@class Book implements IReader{@b@ public String getContent(){@b@ return "从前有个山……";@b@ }@b@}@b@@b@class Farther{@b@ public void narrate(IReader reader){@b@ System.out.println("爸爸开始讲故事");@b@ System.out.println(reader.getContent());@b@ }@b@}@b@@b@public class Client{@b@ public static void main(String[] args){@b@ Farther farther= new Farther();@b@ farther.narrate(new Book());@b@ farther.narrate(new Newspaper());@b@@b@ }@b@}
如果上面对于杂志、书不设计IReader接口,Farther类需针对每种类型的读物实现一个方法,设计实现特别的低级累赘,传递依赖关系有三种方式,以上的例子中使用的方法是接口传递,另外还有两种传递方式:构造方法传递和setter方法传递,相信用过Spring框架的,对依赖的传递方式一定不会陌生。 在实际编程中,我们一般需要做到如下3点: a.低层模块尽量都要有抽象类或接口,或者两者都有。 b.变量的声明类型尽量是抽象类或接口。 c.使用继承时遵循里氏替换原则。总之,依赖倒置原则就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒置。
4. 接口隔离原则(Interface Segregation Principle)
定义:不同接口需要保证相对的独立性,不应该出现两个或以上接口方法有好多重合(降低依赖耦合)
5. 迪米特法则(Law Of Demeter)
定义:类与类之间的关系越密切,耦合度越大,尽量降低类与类之间关系密切程度,使其相对独立,从而降低类之间耦合度(解耦)
举例:现集团公司,下属单位有分公司和直属部门,要打印出所有下属单位的员工ID。先来看违反迪米特法则的设计
//总公司员工@b@class Employee{@b@ private String id;@b@ public void setId(String id){@b@ this.id = id;@b@ }@b@ public String getId(){@b@ return id;@b@ }@b@}@b@//分公司员工@b@class SubEmployee{@b@ private String id;@b@ public void setId(String id){@b@ this.id = id;@b@ }@b@ public String getId(){@b@ return id;@b@ }@b@}@b@class SubCompanyManager{@b@ public List<SubEmployee> getAllEmployee(){@b@ List<SubEmployee> list = new ArrayList<SubEmployee>();@b@ for(int i=0; i<100; i++){@b@ SubEmployee emp = new SubEmployee();@b@ //为分公司人员按顺序分配一个ID@b@ emp.setId("分公司"+i);@b@ list.add(emp);@b@ }@b@ return list;@b@ }@b@}@b@class CompanyManager{@b@@b@ public List<Employee> getAllEmployee(){@b@ List<Employee> list = new ArrayList<Employee>();@b@ for(int i=0; i<30; i++){@b@ Employee emp = new Employee();@b@ //为总公司人员按顺序分配一个ID@b@ emp.setId("总公司"+i);@b@ list.add(emp);@b@ }@b@ return list;@b@ }@b@ @b@ public void printAllEmployee(SubCompanyManager sub){@b@ List<SubEmployee> list1 = sub.getAllEmployee();@b@ for(SubEmployee e:list1){@b@ System.out.println(e.getId());@b@ }@b@@b@ List<Employee> list2 = this.getAllEmployee();@b@ for(Employee e:list2){@b@ System.out.println(e.getId());@b@ }@b@ }@b@}@b@@b@public class Client{@b@ public static void main(String[] args){@b@ CompanyManager e = new CompanyManager();@b@ e.printAllEmployee(new SubCompanyManager());@b@ }@b@}
根据迪米特法设计,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合,具体如下
class SubCompanyManager{@b@ public List<SubEmployee> getAllEmployee(){@b@ List<SubEmployee> list = new ArrayList<SubEmployee>();@b@ for(int i=0; i<100; i++){@b@ SubEmployee emp = new SubEmployee();@b@ //为分公司人员按顺序分配一个ID@b@ emp.setId("分公司"+i);@b@ list.add(emp);@b@ }@b@ return list;@b@ }@b@ public void printEmployee(){@b@ List<SubEmployee> list = this.getAllEmployee();@b@ for(SubEmployee e:list){@b@ System.out.println(e.getId());@b@ }@b@ }@b@}@b@@b@class CompanyManager{@b@ public List<Employee> getAllEmployee(){@b@ List<Employee> list = new ArrayList<Employee>();@b@ for(int i=0; i<30; i++){@b@ Employee emp = new Employee();@b@ //为总公司人员按顺序分配一个ID@b@ emp.setId("总公司"+i);@b@ list.add(emp);@b@ }@b@ return list;@b@ }@b@ @b@ public void printAllEmployee(SubCompanyManager sub){@b@ sub.printEmployee();@b@ List<Employee> list2 = this.getAllEmployee();@b@ for(Employee e:list2){@b@ System.out.println(e.getId());@b@ }@b@ }@b@}
6. 开闭原则(Open Close Principle)
定义:对扩展开放,对修改关闭