设计模式通常用于软件开发过程中,它们提供了软件开发过程中面临的一般问题的最佳解决方案。
0.设计模式的6大原则
总原则-开闭原则
对扩展开放,对修改封闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等
单一职责原则(Single Responsibility Principle,简称SRP)
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分。
里氏替换原则(Liskov Substitution Principle,简称LSP): 子类可以替换父类
继承可以提高代码的重用性(子类拥有父类的方法和属性)和可扩展性,但是增加了耦合(一旦父类有了变动,可能后果造成非常糟糕,要重构大量的代码)。为解决这个问题,引入了LSP:
a.子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
b.子类中可以增加自己特有的方法。
c.当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
1 public class LSP { 2 3 4 class A { 5 public void fun(HashMap map){ 6 System.out.println("父类被执行..."); 7 } 8 } 9 10 class B extends A{ 11 public void fun(Map map){ 12 System.out.println("子类被执行..."); 13 } 14 } 15 16 public static void main(String[] args){ 17 System.out.print("父类的运行结果:"); 18 LSP lsp =new LSP(); 19 LSP.A a= lsp.new A(); 20 HashMap<Object, Object> map=new HashMap<Object, Object>(); 21 a.fun(map); 22 //父类存在的地方,可以用子类替代 23 //子类B替代父类A 24 System.out.print("子类替代父类后的运行结果:"); 25 LSP.B b=lsp.new B(); 26 b.fun(map); 27 } 28 } 29 /* 30 运行结果: 31 父类的运行结果:父类被执行... 32 子类替代父类后的运行结果:父类被执行... 33 符合条件 34 我们应当注意,子类并非重写了父类的方法,而是重载了父类的方法。因为子类和父类的方法的输入参数是不同的。 35 子类方法的参数Map比父类方法的参数HashMap的范围要大,所以当参数输入为HashMap类型时,只会执行父类的方法,不会执行父类的重载方法。这符合里氏替换原则 36 */
1 //将子类方法的参数范围缩小会怎样? 2 3 import java.util.Map; 4 public class A { 5 public void fun(Map map){ 6 System.out.println("父类被执行..."); 7 } 8 } 9 10 import java.util.HashMap; 11 public class B extends A{ 12 public void fun(HashMap map){ 13 System.out.println("子类被执行..."); 14 } 15 } 16 17 import java.util.HashMap; 18 19 public class demo { 20 static void main(String[] args){ 21 System.out.print("父类的运行结果:"); 22 A a=new A(); 23 HashMap map=new HashMap(); 24 a.fun(map); 25 //父类存在的地方,都可以用子类替代 26 //子类B替代父类A 27 System.out.print("子类替代父类后的运行结果:"); 28 B b=new B(); 29 b.fun(map); 30 } 31 } 32 /* 33 运行结果: 34 父类的运行结果:父类被执行... 35 子类替代父类后的运行结果:子类被执行... 36 37 在父类方法没有被重写的情况下,子方法被执行了,这样就引起了程序逻辑的混乱。 38 所以子类中方法的前置条件必须与父类中被覆写的方法的前置条件相同或者更宽松。不符合里式替换*/
d.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
1 public class LSP1 { 2 abstract class A { 3 public abstract Map fun(); 4 } 5 6 class B extends A{ 7 @Override 8 public HashMap fun(){ 9 HashMap b=new HashMap(); 10 b.put("b","子类被执行..."); 11 return b; 12 } 13 } 14 15 public static void main(String[] args){ 16 LSP1 lsp =new LSP1(); 17 LSP1.A a=lsp.new B(); 18 System.out.println(a.fun()); 19 } 20 21 } 22 /* 23 运行结果: 24 {b=子类被执行...}*/
看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?
后果就是:你写的代码出问题的几率将会大大增加。
依赖倒置原则(Dependence Inversion Principle,简称DIP)面向接口编程,多态(接口类或者抽象类)
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖实现类;实现类应该依赖抽象。
对象的依赖关系可以通过三种方法来实现:
- 接口声明依赖对象,接口注入
- 构造函数传递依赖对象,构造函数注入
- setter方法传递依赖对象,setter方法注入
1 package UML; 2 /*在接口处就声明了依赖的对象。司机接口IDriver,其方法drive()的形参是ICar类型的,那么我们可以说IDrive与ICar发生了依赖关系,Dazhong,baoma依赖ICar注入,依赖倒置了。接口声明依赖的方法也叫接口注入。*/ 3 4 //车子接口 5 interface ICar { 6 public void run(); 7 } 8 9 class DaZhong implements ICar{ 10 public void run(){ 11 System.out.println("开大众汽车"); 12 } 13 } 14 15 //宝马车类 16 class BaoMa implements ICar{ 17 public void run(){ 18 System.out.println("开宝马车"); 19 } 20 } 21 22 //司机接口 23 interface IDriver { //接口声明依赖对象,接口注入ICar,这里变成了car依赖ICar 24 public void drive(ICar car); 25 } 26 27 //司机类 28 class Driver implements IDriver{ //依赖接口 29 public void drive(ICar car){ 30 car.run(); 31 } 32 } 33 34 public class Client { 35 public static void main(String[] args){ 36 IDriver Tom=new Driver(); 37 ICar daZhong=new DaZhong(); 38 Tom.drive(daZhong);//Tom开大众汽车 39 40 ICar baoMa=new BaoMa(); 41 Tom.drive(baoMa);//Tom开宝马 42 } 43 }
1.设计模式的分类