程序设计的原则
- 1. 单一设计原则
- 2. 接口隔离原则
- 3. 依赖倒转
- 4. 里氏替换原则
- 5. 开闭原则
- 6. 迪米特原则
- 7. 合成复用
1. 单一设计原则
每一个类只负责做自己的的功能。不能跨越到其它类。
- 不合理
package top.bitqian.principle.single_responsibility;/*** @author echo lovely* @since 2021/4/10 16:45* @description* <p>* 单一设计原则:* <li>降低类的复杂度, 一个类只维护一项职责</li>* <li>提高类的可读性, 可维护性</li>* <li>降低变更引起的风险</li>* <li>* usual, 一般遵守单一设计原则, * 只有逻辑足够简单, 才能<label>代码级别</label>违反单一设计原则, * 如果方法足够少, 可以在方法级别保持单一职责原则。* </li>* </p>*/public class SingleResponsibility1 {public static void main(String[] args) {Vehicle vehicle = new Vehicle();vehicle.run("火车");vehicle.run("汽车");// 飞机在陆地上不合理..vehicle.run("飞机");}/*** vehicle, there is...*/static class Vehicle {public void run(String vehicle) {System.out.println(vehicle + " 在陆地上跑.....");}}
}
- 基于类级别
package top.bitqian.principle.single_responsibility;/*** @author echo lovely* @date 2021/4/10 16:53* @description <P>* 拆分, 将交通工具拆分为多个类, 每个交通工具做各自的事情* 优点:如果每个大职责下, 有很多小的功能, 细节的地方, 比如轮船启动前, 要排水, 飞机要检修... 可以拆* 缺点:当功能少, 只考虑到运行时, 下面的设计显得冗余。* </P>*/public class SingleResponsibility2 {public static void main(String[] args) {// 分为三个类, 各司其职new RoadVehicle().run("汽车");new AirVehicle().run("飞机");new WaterVehicle().run("轮船");}static class RoadVehicle {public void run(String roadVehicle) {System.out.println(roadVehicle + "在路上运行");}}static class AirVehicle {public void run(String airVehicle) {System.out.println(airVehicle + "在天上运行");}}static class WaterVehicle {public void run(String waterVehicle) {System.out.println(waterVehicle + "在水上运行");}}}
- 基于方法级别
package top.bitqian.principle.single_responsibility;/*** @author echo lovely* @date 2021/4/10 17:00* @description* <p>* 方法级别 进行单一设计原则* </p>*/public class SingleResponsibility3 {public static void main(String[] args) {Vehicle vehicle = new Vehicle();vehicle.airRun("飞机");vehicle.roadRun("汽车");vehicle.waterRun("轮船");}static class Vehicle {public void roadRun(String name) {System.out.println(name + "在陆地上run");}public void airRun(String name) {System.out.println(name + "在天空上run");}public void waterRun(String name) {System.out.println(name + "在水上run");}}}
2. 接口隔离原则
每一个接口只负责一件事,记住,接口是规范, 规定某种具体的行为。具体的一件事。
每一接口尽可能只负责一项功能。这里演示接口拆分,将接口拆成多个接口。
- 需求,错误的设计
package top.bitqian.principle.segregation;/*** @author echo lovely* @date 2021/4/10 17:46* @description* <p>* 接口隔离原则, 将接口拆分** 业务:* A, B 实现MyInterface** C 通过 MyInterface依赖(使用)A, operation1, 2, 3三个方法* D 通过 MyInterface依赖B, 使用 operation1, 4, 5三个方法*** </p>*/public class Segregation1 {public static void main(String[] args) {C c = new C();// c中依赖 A中, operation1,2,3 方法c.dependency1(new A());c.dependency2(new A());c.dependency3(new A());// d依赖B operation1,4.,5 方法D d = new D();d.dependencyD1(new B());d.dependencyD2(new B());d.dependencyD3(new B());/*分析: 现在, C 依赖A (1.2.3), D 依赖B (1.4.5),而A中的4,5方法, B中的2,3方法显得多余..*/}/*** 定义接口, 5个方法 操作*/interface MyInterface {void operation1();void operation2();void operation3();void operation4();void operation5();}/*** A 实现 MyInterface*/static class A implements MyInterface {@Overridepublic void operation1() {System.out.println("A operation1");}@Overridepublic void operation2() {System.out.println("A operation2");}@Overridepublic void operation3() {System.out.println("A operation3");}@Overridepublic void operation4() {System.out.println("A operation4");}@Overridepublic void operation5() {System.out.println("A operation5");}}/*** B 实现 MyInterface*/static class B implements MyInterface {@Overridepublic void operation1() {System.out.println("B operation1");}@Overridepublic void operation2() {System.out.println("B operation2");}@Overridepublic void operation3() {System.out.println("B operation3");}@Overridepublic void operation4() {System.out.println("B operation4");}@Overridepublic void operation5() {System.out.println("B operation5");}}// C, D类 通过MyInterface 依赖 A, B/*** 依赖A类 1,2,3*/static class C {public void dependency1(MyInterface i) {i.operation1();}public void dependency2(MyInterface i) {i.operation2();}public void dependency3(MyInterface i) {i.operation3();}}/*** 依赖D 1,4,5*/static class D {public void dependencyD1(MyInterface i) {i.operation1();}public void dependencyD2(MyInterface i) {i.operation4();}public void dependencyD3(MyInterface i) {i.operation5();}}}
上面的接口只定义了一个接口MyInterface, 而这个接口中,并不是子类都需要使用的,
所以可以将这个接口拆分多个接口。让子类选择性的实现多个接口中的某几个。
- 解决
package top.bitqian.principle.segregation.aprove;/*** @author echo lovely* @date 2021/4/10 18:30* @description* <p>* 隔离方案2, 拆分接口* <a href="top.bitqian.principle.segregation.Segregation1">pls see</a>* </p>* @see top.bitqian.principle.segregation.Segregation1*/public class Segregation2 {public static void main(String[] args) {C c = new C();c.dependency1(new A());c.dependency2(new A());c.dependency3(new A());D d = new D();d.dependencyD1(new B());d.dependencyD2(new B());d.dependencyD3(new B());}/*将5个方法的接口, 拆分为3个部分, 让类来实现*/interface MyInterface1 {void operation1();}interface MyInterface2 {void operation2();void operation3();}interface MyInterface3 {void operation4();void operation5();}/*** A 实现1,2,3方法*/static class A implements MyInterface1, MyInterface2 {@Overridepublic void operation1() {System.out.println("A impl operation1");}@Overridepublic void operation2() {System.out.println("A impl operation2");}@Overridepublic void operation3() {System.out.println("A impl operation3");}}/*** B实现了1,4,5 方法*/static class B implements MyInterface1, MyInterface3 {@Overridepublic void operation1() {System.out.println("B impl operation1");}@Overridepublic void operation4() {System.out.println("B impl operation4");}@Overridepublic void operation5() {System.out.println("B impl operation5");}}/*** C 通过拆分的MyInterface1 MyInterface2两个接口 间接的依赖了A类*/static class C {public void dependency1(MyInterface1 i1) {i1.operation1();}public void dependency2(MyInterface2 i2) {i2.operation2();}public void dependency3(MyInterface2 i2) {i2.operation3();}}/*** D 通过拆分的 MyInterface1 MyInterface3两个接口 依赖了B*/static class D {public void dependencyD1(MyInterface1 i1) {i1.operation1();}public void dependencyD2(MyInterface3 i3) {i3.operation4();}public void dependencyD3(MyInterface3 i3) {i3.operation5();}}}
3. 依赖倒转
面向接口编程
- 需求描述
package top.bitqian.principle.dependency_inversion;/*** @author echo lovely* @date 2021/4/11 10:28* @description* <P>* 依赖倒转原则:* 1. 面向接口编程* <ul>* <li>接口作为参数传递</li>* <li>构造方法传递接口</li>* <li>setter传递接口参数</li>* </ul>* 2. 接口, 抽象类的细节越少越好, 细节应该交给子类* 3. 注意点:* <ul>* <li>模块尽量使用抽象类 或者接口, (两者都实现), more stable</li>* <li>变量的声明类型尽量是接口或者抽象类, 在变量的使用和实际对象之间, 存在缓冲层, 有利于程序扩展和优化.</li>* <li>继承遵循里氏替换原则</li>* </ul>* </P>*/public class DemoInversion1 {public static void main(String[] args) {// somebody get a msg from email..// but he just got a email..new Person().receive(new Email());}static class Email {String getInfo() {return "from email: hello, adorable";}}static class Person {void receive(Email email) {String msg = email.getInfo();System.out.println(msg);}}}
- 定义接口,使用接口作为依赖
package top.bitqian.principle.dependency_inversion.improve;/*** @author echo lovely* @date 2021/4/11 10:41* @description <p>* 模仿person接收消息* </p>*/public class DemoInversion2 {public static void main(String[] args) {/** 依赖倒转 like 多态*/Person p = new Person();p.receive(new Email());p.receive(new YouTelegram());}/*** 获取消息接口*/interface IReceiver {String getInfo();}static class Email implements IReceiver {@Overridepublic String getInfo() {return "from email: hello, adorable";}}static class YouTelegram implements IReceiver {@Overridepublic String getInfo() {return "from t.me: hello, bitQian";}}/*** person 接收到消息*/static class Person {void receive(IReceiver receiver) {String info = receiver.getInfo();System.out.println(info);}}}
上面的依赖是通过参数传递, 还可以通过setter方法, 构造器。
4. 里氏替换原则
继承需要遵循这个原则。
1. 如果父类是普通的类(非抽象,接口) 不要改写父的方法实现。
2. 如果重载父类实现的方法, 子类的方法参数必须比父类的大
(父是ArrayList子则是List或者Collection或者Iterator)
3. 重载, 返回要比父的小
4. 父是abstract, 则必须实现父的抽象。子类可以有自己的方法。
package top.bitqian.principle.liskov;import java.util.HashMap;
import java.util.Map;/*** @author echo lovely* @date 2021/4/12 20:34* @description* <p>* 继承缺点:* <p>* 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。* 降低了代码的灵活性。因为继承时,父类会对子类有一种约束。* 增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。* 有时修改了一点点代码都有可能需要对打断程序进行重构。* </p>* 里氏替换原则: <strong>只要有父类出现的地方,都可以用子类来替代</strong>* <li>* pls see <a href="https://www.jianshu.com/p/cf9f3c7c0df5">extends..</a>* </li>* <li>* case1: 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。* case2: 子类中可以增加自己特有的方法。* case3: 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。(形参更大)* case4: 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。(返回更小)* </li>* </p>*/public class Liskov1 {public static void main(String[] args) {A a = new A();System.out.println("3-2=" + a.fun1(3, 2));System.out.println("----------");B b = new B();System.out.println("3-2=" + b.fun1(3, 2));// 执行了父类方法, 但是hello并未被重写... 可能导致程序的混乱..Map<?, ?> map = new HashMap<>();Case3.A a1 = new Case3.A();a1.hello(map);// 执行了子类方法Case3.B b1 = new Case3.B();b1.hello(new HashMap<>());// {}父类的方法b1.hello(map);}static class A {// but that's sub.. 编程时有可能写错int fun1(int a, int b) {return a - b;}}/*** 子类不可实现父类已经实现的方法*/static class B extends A {// add, 子类不要覆盖父类已经实现的方法..@Overrideint fun1(int a, int b) {return a + b;}}static class Case3 {static class A {void hello(Map<?, ?> map) {System.out.println(map + "父类的方法");}}static class B extends Case3.A {// 重载了...void hello(HashMap<?, ?> map) {System.out.println(map + "子类的方法");}}}/*** 子类的返回必须比父类小*/static class Case4 {static abstract class A {abstract Map<?, ?> fun1();}static class B extends Case4.A {/*** 返回值类型必须比父类的小, 否则编译器报错* @return null*/HashMap<?, ?> fun1() {return null;}}}}
- 使用抽象, 抽取, 解决
package top.bitqian.principle.liskov.improve;/*** @author echo lovely* @date 2021/4/12 21:17* @description <p>* 更好的解决方案* </p>*/public class Liskov2 {public static void main(String[] args) {A a = new A();System.out.println("3-2=" + a.fun1(3, 2));System.out.println("----------");B b = new B();System.out.println("3+2=" + b.fun1(3, 2));System.out.println("3-2=" + b.fun2(3, 2));}static abstract class Base {}static class A extends Base {public int fun1(int a, int b) {return a - b;}}/*** 通过继承基类 解耦...*/static class B extends Base {// 依赖 Aprivate final A myA = new A();// B与A 无关 不可能认为是重写 add..int fun1(int a, int b) {return a + b;}int fun2(int a, int b) {return this.myA.fun1(a, b);}}}
5. 开闭原则
- 画图demo
package top.bitqian.principle.open_close;/*** @author echo lovely* @date 2021/4/17 10:04* @description* <p>* ocp 开闭原则...* <li>提供方可扩展, 调用方关闭, 调用方不修改</li>* </p>*/public class Ocp {public static void main(String[] args) {new GraphicEditor(new Circle());new GraphicEditor(new Triangle());}// 调用方static class GraphicEditor {private final Shape shape;GraphicEditor(Shape shape) {this.shape = shape;this.drawCircle();}void drawCircle() {// fixme: 如果我有很多形状, 这里要加很多判断if (shape.type == 1) {System.out.println("draw circle...");}if (shape.type == 2) {System.out.println("draw Triangle...");}}}// 提供方static class Shape {Integer type;}static class Circle extends Shape {Circle() {super.type = 1;}}static class Triangle extends Shape {Triangle() {super.type = 2;}}}
- 通过依赖抽象解决
package top.bitqian.principle.open_close.improve;/*** @author echo lovely* @date 2021/4/17 10:13* @description* <p>* 开闭原则实现demo* </p>*/public class Ocp1 {public static void main(String[] args) {new GraphicEditor(new Circle()).drawShape();new GraphicEditor(new Triangle()).drawShape();}// 调用方static class GraphicEditor {private final Shape shape;GraphicEditor(Shape shape) {this.shape = shape;}/*** draw sth...*/void drawShape() {// 这里可以不用改变了, 只要改变子类的实现细节, 就可画出不同的形状..shape.shapeDetail();}}// 提供方static abstract class Shape {Integer type;/*** 提供抽象*/abstract void shapeDetail();}static class Circle extends Shape {Circle() {super.type = 1;}@Overridevoid shapeDetail() {System.out.println("draw circle...");}}static class Triangle extends Shape {Triangle() {super.type = 2;}@Overridevoid shapeDetail() {System.out.println("draw triangle...");}}}
6. 迪米特原则
抽取, 低耦合, 封装,保持在成员变量,参数,返回值直接的耦合。
不要在方法体里面出现默认类。(别在方法体里面new陌生对象。)
package top.bitqian.principle.demeter;import lombok.Data;import java.util.ArrayList;
import java.util.List;/*** @author echo lovely* @date 2021/4/17 11:27* @description <p>* 迪米特原则:* 最小知道原则, 对依赖的类知道的越少越好, 依赖类只提供public方法给被依赖类访问* 通过方法参数, 方法返回值, 成员变量产生耦合* </p>*/public class Demeter1 {public static void main(String[] args) {//创建了一个 SchoolManager 对象SchoolManager schoolManager = new SchoolManager();//输出学院的员工 id 和 学校总部的员工信息schoolManager.printAllEmployee(new CollegeManager());}//学校总部员工类@Datastatic class Employee {private String id;}//学院的员工类@Datastatic class CollegeEmployee {private String id;}//管理学院员工的管理类static class CollegeManager {//返回学院的所有员工public List<CollegeEmployee> getAllEmployee() {List<CollegeEmployee> list = new ArrayList<>();for (int i = 0; i < 10; i++) { //这里我们增加了 10 个员工到 listCollegeEmployee emp = new CollegeEmployee();emp.setId("学院员工 id= " + i);list.add(emp);}return list;}}//学校管理类//分析 SchoolManager 类的直接朋友类有哪些 Employee、CollegeManager//CollegeEmployee 不是 直接朋友 而是一个陌生类,这样违背了 迪米特法则static class SchoolManager {//返回学校总部的员工public List<Employee> getAllEmployee() {List<Employee> list = new ArrayList<>();for (int i = 0; i < 5; i++) { //这里我们增加了 5 个员工到 listEmployee emp = new Employee();emp.setId("学校总部员工 id= " + i);list.add(emp);}return list;}//该方法完成输出学校总部和学院员工信息(id)void printAllEmployee(CollegeManager sub) {//分析问题//1. 这 里 的 CollegeEmployee 不是 SchoolManager 的直接朋友//2. CollegeEmployee 是以局部变量方式出现在 SchoolManager//3. 违反了迪米特法则// fixeme: 可将下面的这段代码抽取到CollegeManager//获取到学院员工List<CollegeEmployee> list1 = sub.getAllEmployee();System.out.println("------------学院员工------------");for (CollegeEmployee e : list1) {System.out.println(e.getId());}//获取到学校总部员工List<Employee> list2 = this.getAllEmployee();System.out.println("------------学校总部员工------------");for (Employee e : list2) {System.out.println(e.getId());}}}}
7. 合成复用
package top.bitqian.principle.composite;/** @author echo lovely* @date 2021/4/17 15:38* @description* <p>* 合成复用原则: 如果两个类没有多大关系, 不要使用继承关系。* 1. 使用依赖关系, 依赖的类作为方法参数.* 2. 使用聚合关系作为成员变量, 使用构造器, 或者setter方法初始化.* 3. 使用复用关系, 初始化的成员变量.* </p>*/