前言
本文是参照 尚硅谷
、黑马程序员
以及cyc2018
记录的个人学习笔记,仅供记录,不确保严谨性,部分实例代码是根据自己的理解抽象了代码,若不理解,更多具象实例可以参考其他网上实例资料,欢迎讨论学习。
设计模式
在软件工程中,设计模式是对软件设计中普遍存在(反复出现)的各种问题所提出的解决方案。
设计模式的目的
设计模式是为了让程序具有更好的
- 代码重用性:即相同功能的代码不用多次编写
- 可读性:即变成规范性,便于其他程序员的阅读和理解
- 可扩展性/可维护:即需要增加新功能时,非常方便
- 可靠性:即增加新功能后,对原功能没有影响
- 使程序呈现高内聚,低耦合的特性
设计模式的七大原则
程序员在编程时,应当遵守的原则,也是各种设计模式的基础。分为:单一职责原则、接口隔离原则、依赖倒置原则、里氏替换原则、开闭原则、迪米特法则和合成复用原则
核心思想:
- 找出应用中可能需要变化之处,把它们独立除了,不要和那些不需要变化的代码混在一起
- 针对接口编程,而不是针对实现编程
- 为了交互对象之间的松耦合设计而努力
单一职责原则
-
基本介绍
对类来说,一个类应该只负责一项职责,如类A负责两个不同的职责1、职责2。当职责41需求变更而改变A时,可能造成职责2执行错误,使用要将类A分解为类A1、类A2。
-
案例引入
public class SingleResponsibility1 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("摩托车"); vehicle.run("汽车"); vehicle.run("飞机"); } } // 交通工具类 class Vehicle { public void run(String vehicle) { System.out.println(vehicle + " 在公路上运行...."); } }
该方法违反了单一职责原则,解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可
public class SingleResponsibility2 { public static void main(String[] args) { RoadVehicle roadVehicle = new RoadVehicle(); roadVehicle.run("摩托车"); roadVehicle.run("汽车"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飞机"); } } class RoadVehicle { public void run(String vehicle) { System.out.println(vehicle + "公路运行"); } } class AirVehicle { public void run(String vehicle) { System.out.println(vehicle + "天空运行"); } } class WaterVehicle { public void run(String vehicle) { System.out.println(vehicle + "水中运行"); } }
相比方式一,遵循了单一职责原则,但要修改主程序,改动很大。
public class SingleResponsibility3 { public static void main(String[] args) { Vehicle2 vehicle2 = new Vehicle2(); vehicle2.run("汽车"); vehicle2.runWater("轮船"); vehicle2.runAir("飞机"); } } class Vehicle2 { public void run(String vehicle) { System.out.println(vehicle + " 在公路上运行...."); } public void runAir(String vehicle) { System.out.println(vehicle + " 在天空上运行...."); } public void runWater(String vehicle) { System.out.println(vehicle + " 在水中行...."); } }
这种修改方法没有对原来的类做大的修改,只是增加方法,虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责
-
注意事项和细节
- 降低类的复杂度,一个类只负责一个职责
- 提高类的可读性、可维护性
- 降低变更引起的风险
- 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类方法足够少,可以在方法级别保持单一职责原则
接口隔离原则
-
基本介绍
客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应建立在最小的接口上。
-
案例引入
interface Interface1 { void operation1(); void operation2(); void operation3(); void operation4(); void operation5(); } class B implements Interface1 { public void operation1() { System.out.println("B 实现了 operation1"); } public void operation2() { System.out.println("B 实现了 operation2"); } public void operation3() { System.out.println("B 实现了 operation3"); } public void operation4() { System.out.println("B 实现了 operation4"); } public void operation5() { System.out.println("B 实现了 operation5"); } } class D implements Interface1 { public void operation1() { System.out.println("D 实现了 operation1"); } public void operation2() { System.out.println("D 实现了 operation2"); } public void operation3() { System.out.println("D 实现了 operation3"); } public void operation4() { System.out.println("D 实现了 operation4"); } public void operation5() { System.out.println("D 实现了 operation5"); } } class A { //A 类通过接口Interface1 依赖(使用) B类,但是只会用到1,2,3方法 public void depend1(Interface1 i) { i.operation1(); } public void depend2(Interface1 i) { i.operation2(); } public void depend3(Interface1 i) { i.operation3(); } } class C { //C 类通过接口Interface1 依赖(使用) D类,但是只会用到1,4,5方法 public void depend1(Interface1 i) { i.operation1(); } public void depend4(Interface1 i) { i.operation4(); } public void depend5(Interface1 i) { i.operation5(); } }
类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D,如果接口 Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法。
按接口隔离原则,应该将接口分解成多个接口,分别让A、C去与所需接口建立联系。改进如下:
public class Segregation1 {
public static void main(String[] args) {
A a = new A();
a.depend1(new B()); // A类通过接口去依赖B类
a.depend2(new B());
a.depend3(new B());
C c = new C();
c.depend1(new D()); // C类通过接口去依赖(使用)D类
c.depend4(new D());
c.depend5(new D());
}
}
// 接口1
interface Interface1 {
void operation1();
}
// 接口2
interface Interface2 {
void operation2();
void operation3();
}
// 接口3
interface Interface3 {
void operation4();
void operation5();
}
class B implements Interface1, Interface2 {
public void operation1() {
System.out.println("B 实现了 operation1");
}
public void operation2() {
System.out.println("B 实现了 operation2");
}
public void operation3() {
System.out.println("B 实现了 operation3");
}
}
class D implements Interface1, Interface3 {
public void operation1() {
System.out.println("D 实现了 operation1");
}
public void operation4() {
System.out.println("D 实现了 operation4");
}
public void operation5() {
System.out.println("D 实现了 operation5");
}
}
class A {
// A 类通过接口Interface1,Interface2 依赖(使用) B类,但是只会用到1,2,3方法
public void depend1(Interface1 i) {
i.operation1();
}
public void depend2(Interface2 i) {
i.operation2();
}
public void depend3(Interface2 i) {
i.operation3();
}
}
class C {
// C 类通过接口Interface1,Interface3 依赖(使用) D类,但是只会用到1,4,5方法
public void depend1(Interface1 i) {
i.operation1();
}
public void depend4(Interface3 i) {
i.operation4();
}
public void depend5(Interface3 i) {
i.operation5();
}
}
依赖倒转/倒置原则
-
基本介绍
- 高层模块不应该依赖底层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒置的中心思想是面向接口编程
- 设计理念是:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的框架比以细节为基础的架构要稳定的多。在Java中,抽象是指接口或抽象类,细节是指具体实现类
- 使用接口或抽象类的目的是规定好规范而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
-
案例引入
public class DependencyInversion { public static void main(String[] args) { Person person = new Person(); person.receive(new Email()); } } class Email { public String getInfo() { return "电子邮件信息: hello,world"; } } class Person { public void receive(Email email ) { System.out.println(email.getInfo()); } }
对于
person
获取对象太过单调,只能获Email
类对象,所以应该引入一个抽象的接口IReceiver
, 表示接收者, 这样Person
类与接口IReceiver
发生依赖public class DependencyInversion { public static void main(String[] args) { //客户端无需改变 Person person = new Person(); person.receive(new Email()); person.receive(new WeiXin()); } } //定义接口 interface IReceiver { public String getInfo(); } class Email implements IReceiver { public String getInfo() { return "电子邮件信息: hello,world"; } } //增加微信 class WeiXin implements IReceiver { public String getInfo() { return "微信信息: hello,ok"; } } class Person { //这里我们是对接口的依赖 public void receive(IReceiver receiver ) { System.out.println(receiver.getInfo()); } }
-
关系传递的三种方式
-
接口传递
// 开关的接口 interface IOpenAndClose { public void open(ITV tv); //抽象方法,接收接口 } interface ITV { //ITV接口 public void play(); } class ChangHong implements ITV { public void play() { System.out.println("长虹电视机,打开"); } } // 实现接口 class OpenAndClose implements IOpenAndClose{ public void open(ITV tv){ tv.play(); } } ==================测试方法========================== ChangHong changHong = new ChangHong(); OpenAndClose openAndClose = new OpenAndClose(); openAndClose.open(changHong);
-
构造器传递
interface IOpenAndClose { public void open(); //抽象方法 } interface ITV { //ITV接口 public void play(); } class OpenAndClose implements IOpenAndClose{ public ITV tv; //成员 public OpenAndClose(ITV tv){ //构造器 this.tv = tv; } public void open(){ this.tv.play(); } } ==================测试方法========================== ChangHong changHong = new ChangHong(); OpenAndClose openAndClose = new OpenAndClose(changHong); openAndClose.open();
-
setter方法传递
interface IOpenAndClose { public void open(); // 抽象方法 public void setTv(ITV tv); } interface ITV { // ITV接口 public void play(); } class OpenAndClose implements IOpenAndClose { private ITV tv; public void setTv(ITV tv) { this.tv = tv; } public void open() { this.tv.play(); } } class ChangHong implements ITV { public void play() { System.out.println("长虹电视机,打开"); } } ==================测试方法========================== ChangHong changHong = new ChangHong(); OpenAndClose openAndClose = new OpenAndClose(); openAndClose.setTv(changHong); openAndClose.open();
-
-
注意事项和细节
- 低层模块尽量都要有抽象类或接口,或两者都有,程序稳定性更好
- 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
- 继承时遵循里氏替换原则
里氏替换原则
-
基本介绍
- 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。所有引用基类的地方必须能透明地使用其子类的对象。
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
- 继承实际上让两个类耦合性增强了,在适当情况下,可以通过聚合,组合,依赖来解决问题
-
案例引入
public class Liskov { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.func1(11, 3)); System.out.println("1-8=" + a.func1(1, 8)); System.out.println("-----------------------"); B b = new B(); System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出11-3 System.out.println("1-8=" + b.func1(1, 8));// 1-8 System.out.println("11+3+9=" + b.func2(11, 3)); } } // A类 class A { // 返回两个数的差 public int func1(int num1, int num2) { return num1 - num2; } } // B类继承了A // 增加了一个新功能:完成两个数相加,然后和9求和 class B extends A { //这里,重写了A类的方法, 可能是无意识 public int func1(int a, int b) { return a + b; } public int func2(int a, int b) { return func1(a, b) + 9; } }
调用B方法时,想使用父类A的求差,可无意识的把A类的方法给重写覆盖了,所以导致出错。可以通过原来的父类和子类都继承一个更通俗的基类,去除原有的继承关系,采用依赖、聚合、组合等关系代替来解决。这就是里氏替换
public class Liskov { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.func1(11, 3)); System.out.println("1-8=" + a.func1(1, 8)); System.out.println("-----------------------"); B b = new B(); //因为B类不再继承A类,因此调用者,不会再func1是求减法 //调用完成的功能就会很明确 System.out.println("11+3=" + b.func1(11, 3));//这里本意是求出11+3 System.out.println("1+8=" + b.func1(1, 8));// 1+8 System.out.println("11+3+9=" + b.func2(11, 3)); //使用组合仍然可以使用到A类相关方法 System.out.println("11-3=" + b.func3(11, 3));// 这里本意是求出11-3 } } //创建一个更加基础的基类 class Base { //把更加基础的方法和成员写到Base类 } // A类 class A extends Base { // 返回两个数的差 public int func1(int num1, int num2) { return num1 - num2; } } // B类继承了A // 增加了一个新功能:完成两个数相加,然后和9求和 class B extends Base { //如果B需要使用A类的方法,使用组合关系 private A a = new A(); //这里,重写了A类的方法, 可能是无意识 public int func1(int a, int b) { return a + b; } public int func2(int a, int b) { return func1(a, b) + 9; } //我们仍然想使用A的方法 public int func3(int a, int b) { return this.a.func1(a, b); } }
开闭原则
-
基本介绍
- 编程中最基础、最重要的设计原则
- 一个软件实体 如类,模块和函数应该对扩展开放【设计方】,对修改关闭【使用方】。用抽象构建框架,用实现扩展细节
- 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
- 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则
-
案件引入
<