什么是设计模式
- 设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
为什么要学习设计模式
- 看懂源代码:如果你不懂设计模式去看Jdk、Spring、SpringMVC、IO等等等等的源码,你会很迷茫,你会寸步难行
- 看看前辈的代码:你去个公司难道都是新项目让你接手?很有可能是接盘的,前辈的开发难道不用设计模式?
- 编写自己的理想中的好代码:我个人反正是这样的,对于我自己开发的项目我会很认真,我对他比对我女朋友还好,把项目当成自己的儿子一样
设计模式分类
- 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式的六大原则
开放封闭原则(open close Principle)
- 原则思想:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有的代码来完成变化
- 优点:单一原则告诉我们,每个类都有自己负责的职责,里氏替换原则不能破坏继承关系的体系。
里氏替换原则(Liskov Substitution Principle)
- 原则思想:使用的基类可以在任何地方使用继承的子类,完美的替换基类
- 描述:子类可以扩展父类的功能,但不能改变父类原有的 功能。子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,子类中可以增加自己特有的方法。
- 优点:增加程序的健壮性,即使增加了子类,原有的子类还可以继续运行,互不影响。
1、子类可以实现父类的抽象方法,但不能覆盖父类的抽象方法,父类中已经实现好的方法,实际上是在设定一系列规范和契约,虽然它不强求所有子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。举例:
public class C {
public int func(int a, int b){
return a+b;
}
}
public class C1 extends C{
@Override
public int func(int a, int b) {
return a-b;
}
}
public class Client{
public static void main(String[] args) {
C c = new C1();
System.out.println("2+1=" + c.func(2, 1));
}
}
运行结果:2+1=1
上面的运行结果明显是错误的。类C1继承C,后来需要增加新功能,类C1并没有新写一个方法,而是直接重写了父类C的func方法,违背里氏替换原则,引用父类的地方并不能透明的使用子类的对象,导致运行结果出错。
2、子类可以有自己的个性
在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能。前面其实已经提到,当功能扩展时,子类尽量不要重写父类的方法,而是另写一个方法,所以对上面的代码加以更改,使其符合里氏替换原则,代码如下:
public class C {
public int func(int a, int b){
return a+b;
}
}
public class C1 extends C{
public int func2(int a, int b) {
return a-b;
}
}
public class Client{
public static void main(String[] args) {
C1 c = new C1();
System.out.println("2-1=" + c.func2(2, 1));
}
}
运行结果:2-1=1
3、覆盖或实现父类的方法时输入参数可以被放大
当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松,通过代码来讲解一下
public class ParentClazz {
public void say(CharSequence str) {
System.out.println("parent execute say " + str);
}
}
public class ChildClazz extends ParentClazz {
public void say(String str) {
System.out.println("child execute say " + str);
}
}
/**
* 测试
*/
public class Main {
public static void main(String[] args) {
ArrayList list = new ArrayList();
ParentClazz parent = new ParentClazz();
parent.say("hello");
ChildClazz child = new ChildClazz();
child.say("hello");
}
}
// 执行结果:
// parent execute say hello
// child execute say hello
以上代码中我们并没有重写父类的方法,只是重载了同名方法,具体的区别是:子类的参数 String 实现了父类的参数 CharSequence。此时执行了子类方法,在实际开发中,通常这不是我们希望的,父类一般是抽象类,子类才是具体的实现类,如果在方法调用时传递一个实现的子类可能就会产生非预期的结果,引起逻辑错误,根据里氏替换的子类的输入参数要宽于或者等于父类的输入参数,我们可以修改父类参数为String,子类采用更宽松的 CharSequence,如果你想让子类的方法运行,就必须覆写父类的方法。代码如下:
public class ParentClazz {
public void say(String str) {
System.out.println("parent execute say " + str);
}
}
public class ChildClazz extends ParentClazz {
public void say(CharSequence str) {
System.out.println("child execute say " + str);
}
}
public class Main {
public static void main(String[] args) {
ParentClazz parent = new ParentClazz();
parent.say("hello");
ChildClazz child = new ChildClazz();
child.say("hello");
}
}
// 执行结果:
// parent execute say hello
// parent execute say hello
4、覆盖或实现父类的方法时输出结果可以被缩小
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。代码实现案例如下:
public abstract class Father {
public abstract Map hello();
}
public class Son extends Father {
@Override
public Map hello() {
HashMap map = new HashMap();
System.out.println("son execute");
return map;
}
}
public class Main {
public static void main(String[] args) {
Father father = new Son();
father.hello();
}
}
// 执行结果:
// son execute
依赖倒转原则(Dependence Inversion Principle)
- 依赖倒置原则的核心思想是面向接口编程
- 依赖倒置原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象类
- 这个是开放封闭原则的基础,具体内容是:对接口编程,依赖于抽象而不依赖于具体。
- 撒旦发射点发射点
1、每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备
2、变量的表面类型尽量是接口或者抽象类
3、任何类都不应该从具体类派生
4、尽量不要覆写基类的方法
如果基类是一个抽象类,而这个方法已经实现了,子类尽量不要覆写。类间依赖的是抽象,覆写了抽象方法,对依赖的稳定性会有一定的影响。
5、结合里氏替换原则使用
里氏替换原则:父类出现的地方子类就能出现。结合本章我们得出了一个通俗的规则:接口负责定义public属性和方法,并且声明与其他对象的依赖关系。抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。
public interface IReader{
//阅读者
void Read(IRead read);
}
public interface IRead{
//被阅读物
void Read();
}
//文学经典类
public class LiteraryClassic : IRead{
//阅读文学经典
public void Read(){
Console.WriteLine("阅读文学经典,滋润内心心灵!");
}
}
//小说类
public class Novel : IRead{
//阅读小说
public void Read(){
Console.WriteLine("阅读小说,放松一下!");
}
}
//小明类
public class XiaoMing : IReader{
//阅读
public void Read(IRead read){
read.Read();
}
}
static void Main(string[] args){
XiaoMing xiaoming = new XiaoMing();//实例化对象小明
IRead literaryClassic = new LiteraryClassic();
//小明阅读文学经典
xiaoming.Read(literaryClassic);
IRead novel = new Novel();
//小明阅读小说
xiaoming.Read(novel);
}
接口隔离原则(Interface Segregation Principle)
- 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了生计和维护方便。所以上文中多次出现:降低依赖,降低耦合。
迪米特法则(最少知道原则)(Demeter Principle)
定义
迪米特法则(Law of Demeter, LoD)是1987年秋天由lan holland在美国东北大学一个叫做迪米特的项目设计提出的,它要求一个对象应该对其他对象有最少的了解,所以迪米特法则又叫做最少知识原则(Least Knowledge Principle, LKP)。
单一职责原则(Principle of single responsibility)
意义
迪米特法则的意义在于降低类之间的耦合。由于每个对象尽量减少对其他对象的了解,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。值得一提的是,这一法则却不仅仅局限于计算机领域,在其他领域也同样适用。比如,美国人就在航天系统的设计中采用这一法则。
- 原则思想:一个方法只负责一件事情。
- 描述:单一职责原则很简单,一个方法 一个类只负责一个职责,各个职责的程序改动,不影响其它程序。 这是常识,几乎所有程序员都会遵循这个原则。
- 优点:降低类和类的耦合,提高可读性,增加可维护性和可拓展性,降低可变性的风险。
public class ParentClazz {
public void say(String str) {
System.out.println("parent execute say " + str);
}
}
public class ChildClazz extends ParentClazz {
public void say(CharSequence str) {
System.out.println("child execute say " + str);
}
}
public class Main {
public static void main(String[] args) {
ParentClazz parent = new ParentClazz();
parent.say("hello");
ChildClazz child = new ChildClazz();
child.say("hello");
}
}
// 执行结果:
// parent execute say hello
// parent execute say hello