设计模式?(Design Pattern)

软件设计中普遍存在的各种问题,所提出的解决方案


目的:

  1. 代码重用性
  2. 可读性(代码具有规范性)
  3. 可扩展性(增加新功能,比较方便)
  4. 可靠性(增加新功能,对原先的功能没有影响)
  5. 低耦合、高内聚

七大原则(设计模式设计的依据)

单一职责(singleResponsibility)

对类来说,一个类只负责一类职责(比如userDao)如果A类负责两个职责,则需要将A类的粒度分解为A1、A2

效果:

  • 降低类的复杂度
  • 提高类的可读性
  • 降低变更引起的风险

接口隔离原则(Interface Segregation Principle)

客户端不应该依赖它不需要的接口,也就是说一个类对另一个类的依赖应该建立在最小的接口上


思考:接口隔离其实是对一个接口的分解的过程(接口A拆分成A1、A2…)

依赖倒转原则(Dependency Inversion Principle)/【面向接口编程】

what:

  1. 抽象不依赖细节,细节应该依赖抽象
  2. 高层模块不依赖底层模块,二者都依赖于抽象
  3. 使用接口或者抽象类去指定规范,具体实现则交给他们的实现类去完成

里氏替换原则(Liskov Substitution Principle)/【LSP原则】

子类可以扩展父类的功能,但不能改变父类原有的功能

How:

原来的父类和子类都继承一个基类,去掉原有的继承关系,采用依赖、聚合、组合的关系来替代

开闭原则(Open Closed Principle)

何为开闭?

  • 开:一个软件实体(类、模块、函数)对其扩展是开放的
  • 闭:对其修改是关闭的

思考:大白话讲就是,如果我们想要给软件新增一些功能,我们应该在不影响(改动)源代码的情况下去增加功能

迪米特法则(最少知道原则)(Demeter Principle)

  1. 一个对象应该对其他对象保持最少的了解(A对象在使用B对象时只关心调用,不关心B对象的实现的细节)
  2. 类之间的关系越密切,耦合度越高

一个栗子:

若A、B两个类有耦合关系,那么B类就不要以局部变量的形式出现在A类中。如果出现,A、B的关系就是非直接朋友关系。反之,若B类作为A的成员变量或者方法的参数再或是方法的返回值,则称A、B的关系是直接朋友关系

合成复用原则(Composite Reuse Principle)

尽量用合成/聚合的方式,而不是使用继承

(后续可补充)

总结

  • 针对接口编程,而不是针对实现编程
  • 解决对象之间的松耦合设计
  • 将不用变化的代码独立出来,在以后的编程过程中尽量扩展而不是修改(个人理解)

正餐

创建型模式:单独对对象的创建进行研究,从而能够高效地创建对象

  • 单例模式
  • 抽象工厂模式
  • 原型模式
  • 建造者模式
  • 工厂模式

结构型模式:设计对象的结构、继承和依赖关系

  • 适配器模式
  • 桥接模式
  • 装饰模式
  • 组合模式
  • 外观模式
  • 享元模式
  • 代理模式

行为型模式:设计对象的行为,使对象的工作效率提高

  • 模板方法模式
  • 命令模式
  • 访问者模式
  • 迭代器模式
  • 观察者模式
  • 中介者模式
  • 备忘录模式
  • 解释器模式
  • 状态模式
  • 策略模式
  • 责任链模式

①单例模式(Singleton)

下定义:采取一定方法在整个软件系统中,对某个类只能存在一个对象实例

它の几种创建方式:

饿汉式

package singleton.type1;

public class demo {
    public static void main(String[] args) {
        singleton s1=singleton.getSingleton();
        singleton s2=singleton.getSingleton();
        System.out.println(s1==s2);
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
    }
}
//所谓饿汉,就是不管三七二十一,在类加载的时候就已经创建好单例对象了
class singleton{
    private static final singleton singleton=new singleton();//使用静态变量去创建单例对象

    private singleton(){//构造方法私有化,防止外部new对象

    }
    public static singleton getSingleton(){//向外提供一个方法获取单例对象
        return singleton;
    }
}

package singleton.type2;

public class demo {
    public static void main(String[] args) {
        singleton s1= singleton.getSingleton();
        singleton s2= singleton.getSingleton();
        System.out.println(s1==s2);
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
    }
}

class singleton{

    private static singleton singleton;

    private singleton(){//构造方法私有化,防止外部new对象

    }

    static{
        singleton=new singleton();//使用静态代码块创建单例对象
    }

    public static singleton getSingleton(){//向外提供一个方法获取单例对象
        return singleton;
    }

}

注意:对于饿汉式,因为在类加载时就已经创建,若不使用单例对象,可能造成内存的浪费


懒汉式(单线程且不安全)

package singleton.type3;

public class demo {
    public static void main(String[] args) {
        singleton s1=singleton.getSingleton();
        singleton s2=singleton.getSingleton();
        System.out.println(s1==s2);
    }
}

class singleton{
    private static singleton singleton;
    private singleton(){ }

    public static singleton getSingleton(){//所谓懒汉就是,只有要用到单例对象时才创建,不用就不创建。就像青蛙一样,你戳它一下 它就往前跳一下
        if(singleton==null){
            singleton=new singleton();
        }
        return singleton;
    }
}

注意:如果遇到这一种情况:线程 1 运行到instance = new Singleton()处的时候,线程 2 抢到的 CPU 的执行权,进入 getInstance() 方法,也运行了instance = new Singleton(),但线程 2 创建了对象这件事情,线程 1 压根儿不晓得,等到线程 1 重新获得 CPU 执行权的时候,从原先的地方 处继续执行,又运行了 instance = new Singleton(); 这行代码,这样就会存在多个对象,得到singleton就不再唯一,这样就失去了原本的意义


最完美的创建方式来啦!使用枚举!

package singleton.typeEnum;

public class demo {


    public static void main(String[] args) {
        singleton s1=singleton.SINGLETON;
        singleton s2=singleton.SINGLETON;
        System.out.println(s1==s2);
        s1.test();
    }

}

enum singleton{
    SINGLETON;
    public void test(){
        System.out.println("is singleton...");
    }
}

使用枚举的方式实现单例模式是《Effective Java》作者力推的方式,在很多优秀的开源代码中经常可以看到使用枚举方式实现单例模式的(身影),枚举类型不允许被继承,同样是线程安全的且只能被实例化一次


除此之外,还能使用静态内部类的方式来实现单例模式

package singleton.type4;

public class demo {
    public static void main(String[] args) {
        singleton s1=singleton.getInstance();
        singleton s2=singleton.getInstance();
        System.out.println(s1==s2);
    }
}

class singleton {
    private singleton(){}

    private static class getSingleton{//创建内部类
        private static final singleton instance=new singleton();//将单例对象设置为内部类成员变量
    }
    public static singleton getInstance(){//对外开放一个获取单例对象
        return getSingleton.instance;
    }
}

工厂模式

简单工厂模式(())

下定义:是由一个工厂对象决定创建出哪一种产品类的实例。定义一个创建对象的类,由这个类去封装实例化对象的行为

典型案例:订购pizza业务

pizza工厂:

package factory.simpleFactory.pizzaStore.simplePizzaFactory;

import factory.simpleFactory.pizzaStore.pizza.pizza;
import factory.simpleFactory.pizzaStore.pizza.spicyPizza;

import java.util.Scanner;

public class pizzaFactory {
    public pizza createPizza(){
        System.out.println("******使用简单工厂模式...******");
        System.out.println("请选择要制作的pizza...");
        pizza pizza=null;
        String type=getType();
        if(type.equals("spicy")){
            pizza=new spicyPizza();
            pizza.setName(type);
            System.out.println(type+"Pizza制作完成...");
        }
        else if(type.equals("NewOrleans")){
            pizza=new spicyPizza();
            pizza.setName(type);
            System.out.println(type+"Pizza制作完成...");
        }
        else {
            System.out.println("制作失败,暂无此种类...");
        }
        return pizza;
    }




    private String getType(){//获取要制造的pizza的种类
        String type;
        Scanner sc=new Scanner(System.in);
        type=sc.next();
        return type;
    }
}

订购pizza业务:

package factory.simpleFactory.pizzaStore;

import factory.simpleFactory.pizzaStore.pizza.pizza;
import factory.simpleFactory.pizzaStore.simplePizzaFactory.pizzaFactory;

public class orderPizza {
    pizzaFactory pizzaFactory;

    orderPizza(pizzaFactory pizzaFactory){
        this.pizzaFactory=pizzaFactory;
    }

    public pizza order(){
       return pizzaFactory.createPizza();
    }

    public static void main(String[] args) {
        orderPizza orderPizza=new orderPizza(new pizzaFactory());
        pizza pizza=orderPizza.order();
        while(pizza!=null){
            pizza=orderPizza.order();
        }
    }
}

这里没有放出pizza类…

结果如下图所示:

②工厂方法模式(简单工厂的进阶)

下定义:定义一个创建子类的抽象方法,由子类决定要实例化的类。简单工厂模式是在工厂内部就实例化需要的类,而工厂方法则是将对象的实例化的权限交给了子类,一般来说,这一个子类对应一类产品

③抽象工厂模式(工厂方法的进阶)

下定义:定义一个接口用于创建相关的对象簇,而无需指定具体的类


Hold on,什么是簇?簇的意思就是聚集的一团东西,就比如华为产品簇(包含手机,电脑,智能穿戴设备等),在这里就是指多个产品(类)上图中的各个pizza。OrderPizza就是抽象工厂,它旗下又有BJOrderPizza和LDOrderPizza这两个具体生产Pizza的子类。其实严格来说抽象工厂的具体工厂所实现的类都是不同类别的。上面的例子最终都是和pizza有关的。这个例子不能很好的区别抽象工厂和工厂方法的区别。

关于工厂模式的思考与理解

学完了工厂设计模式,我明白了此类设计模式的思想就是来源于现实生活 。我看看我显示屏旁的机箱里的电脑配件…

配一台电脑不就要经过工厂模式的一系列操作么。

下面开始讲故事:

张三(三迟但到)是个计算机天才aka装B猿,呸,装机猿。刚开始装机数量不是特别多,他一个人也应付得过来且口碑也不错。但是随着客户的数量的增加,他一个人有点吃不消了。因此他想,”为什么开一个工厂来专门装电脑呢?“。很快他找银行贷了一笔款用于新建工厂(这就是简单工厂设计模式)。工厂内部负责生产。效率相比以前大大提升(利用工厂生成需要的对象)


但是过了一阵子,张三发现每个用户的装机要求不尽相同,有人想要一台配置不高的办公电脑,而有人是臭打游戏的(比如我),他们想要一台配置高的发烧电脑。因此他们对配件的要求也不一样。可是工厂只有一个,所有配件的生产都在这里进行,同时工厂场地有限(类の大小),虽然能扩建但是较为麻烦(在一个工厂类不断地添加新的代码)。很快张三想到了解决方案:再开设几个工厂生产不同的整机(具体实现类),这几个工厂都要听总工厂的指示(实现总工厂的抽象方法),生产符合不同配置需求的整机(产品对象)张三很快就做强做大,总工厂也变成了大公司。


当然大公司也有大公司的麻烦,随着计算机硬件的高速发展,人们不再满足于购买整机,他们更加偏向于DIY:自己买配件来组装电脑。张三注意到了这个现象,所以他联系许多硬件的生产厂商,拿到他们的销售权。最后原先的工厂不再组装整机,而是组装加工配件。比如GPU工厂、CPU工厂、主板工厂…(具体工厂),如果在后续还有新的电脑硬件需求,只要再开设一间新的代工厂(新的具体工厂)就好了


上面的三种发展历程对应的是工厂模式的演变过程。从一个人全干到交给工厂再到交给多个工厂完成。可以看出,生产一个简单产品,我们可以用简单工厂模式,但是如果产品需要扩展,我们就需要工厂方法模式。如果一个产品不够还需要多种类别的产品,也就是产品簇,我们就要采取抽象工厂模式。


最后我想说,设计模式源于生活!生活中的例子能够加深我对不同设计模式的理解

④原型模式(Prototype)对象.clone()

下定义:用原型实例执行创建对象的种类,而且通过拷贝这些原型,创建新的对象

优点:

  1. 创建新的对象比较复杂时,可以利用原型模式来简化对象的创建过程,同时也能提高效率
  2. 不同重复新初始化对象,而是动态地获得对象运行时的状态
  3. 如果原始对象发生变化,比如增加属性或者删除属性,其他克隆对象也能发生相应的变化,无需修改代码

缺点:

需要为每一个类配备一个克隆方法,对旧的类需要修改其源代码,这违背了OCP原则

⑤建造者模式(Builder)

下定义:生成器模式,是一种对象构建模式,他可以将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方法可以构造不同表现的对象

建造者模式的四个角色

  1. Product(产品角色):一个具体的产品对象
  2. Builder(抽象建造者):创建一个Product对象的各个部件指定的接口或抽象类
  3. ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件
  4. Director(指挥者):构建一个是使用Builder接口的对象,它主要用于创建一个复杂对象,它主要有两个作用,隔离客户和对象的生产过程负责控制对象的生产过程

————————————-人工分界线————————————-

⑥适配器模式(Adapter)

下定义:将某个类的接口转换成客户端期望的另一个接口表示,主要是为了兼容性,让原本接口不匹配的两个类能够协同工作,适配器又名为包装器(Wrapper)

一般来说适配器的作用就是使src类和target类发生联系:target——adapter——src

适配器有三种类型:

  1. 类适配器模式【适配器继承src类】
  2. 对象适配器模式(最常用)【适配器持有src类】
  3. 接口适配器模式

⑦桥接模式(Bridge)

下定义:将实现与抽象放在两个不同的层次,是这两个层次能够独立改变

作用:替代多层继承方案,有效地避免了类爆炸问题,可以减少子类的数量,降低维护系统的维护成本

但是,该模式的引入会增加对系统的理解和设计难度,再者该模式还需要正确识别出系统中两个独立变化的维度(抽象和实现)

⑧装饰者模式(晕…)

下定义:动态的将新功能附加到对象上。在功能拓展上,比继承更有弹性,装饰者模式也体现了OCP原则

现实情景:快递包装、咖啡加料…

⑨组合模式(Composite)/部分整体

下定义:它创建了对象组的树形结构,将对象组合成树形结构以表示“整体-部分”的层次关系

使用情景:当我们要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子节点进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子

具体场景:学校院系展示

注意:如果节点和叶子具有较大的差异性,比如方法和属性很多都不一样,则不适合组合模式

⑩外观模式(Facade)/最简单的设计模式,原来以前使用过

下定义:也称过程模式,为子系统中的一组接口提供一个一致的界面。此模式定义了一个高层接口,这个接口使得这一子系统容易使用

⑪享元模式(Flyweight)

下定义:又称蝇量模式,使用共享技术有效支持大量细粒度的对象,常用于系统的底层开发,比如数据库连接池、String常量池等

享元模式能够有效解决重读对象的内存的浪费问题,比如数据库连接池的connection

 其中,Flyweight是抽象享元角色。它是产品的抽象类,同时定义出对象的外部状态和内部状态(外部状态及内部状态相关内容见后方)的接口或实现;ConcreteFlyweight是具体享元角色,是具体的产品类,实现抽象角色定义的业务;UnsharedConcreteFlyweight是不可共享的享元角色,一般不会出现在享元工厂中;FlyweightFactory是享元工厂,它用于构造一个池容器,同时提供从池中获得对象的方法。

⑫代理模式

注意区别于装饰模式

下定义:为一个对象提供一个替身,控制这个对象的访问,即通过代理对象访问目标对象,这样做的好处是可以在目标对象实现的基础上,增强额外的功能操作,进而做到扩展目标对象的功能

代理分类:

静态代理

最简单的代理,有手就行(雀氏雀氏)

proxy代理对象和target目标对象共同实现一个interface接口,然后将代理对象内部聚合目标对象,然后即可在调用目标对象的目标方法前后进行各种操作。

但是,不要高兴得太早,因为代理对象和目标对象要实现一样的接口,所以会有很多的代理类,并且一旦接口增加了方法,目标对象和代理对象都要进行维护…(维护?狗都不干)

动态代理(JDK代理)

代理对象不需要实现接口,但是目标对象要实现接口

cglib代理(可在内存中动态创建对象,不需要实现接口)

————————————-人工分界线(以下是行为型设计模式)————————————-

⑬模板方法模式(Template Method)

注意区别于外观模式

下定义:在一个抽象类公开定义了执行它的方法的模板,它的子类按需要重写方法实现,但调用将以抽象类中定义的方式进行

现实生活中的例子:小时候没少背一些作文模板吧!不同文体是什么样的模板我不用解释了吧(反正现在也忘得差不多了- -)

⑭命令模式(Command)

下定义:在命令模式中存在着这样一条关系链:

传统的调用方式:

调用者——被调用者

在命令模式下:

调用者(Invoker)——命令(Command)——被调用者(Receiver)

很显然我们能够看出,命令模式能够很好的消除调用者与被调用者之间的耦合(实现解耦)

现实情景:打仗的时候,将军发布进攻命令,众将士发起冲锋

主要实现方式(类图):

(待补充…)

⑮访问者模式/(看不懂了….)

下定义:null

⑯迭代者模式(Iterator)

下定义:如果我们的集合元素是用不同的方式实现的,比如数组,集合类等,当客户端要遍历这些元素就会使用到不同的遍历方式,而且会暴露元素的内部结构,可以考虑使用迭代器模式解决

提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示

主要目的:将管理对象集合和遍历对象集合的责任分开

  1. Iterator:迭代器接口,是系统提供,含义hasNext,next,remove
  2. ConcreteIterator:具体的迭代器类
  3. Aggregate:统一的聚合接口,将客户端和具体聚合解耦
  4. ConcreteAggregate:具体的聚合,持有对象集合

⑰观察者模式(Observer)/太简单了

下定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。比如社交账号上的订阅或者关注按钮,多个用户关注同一个up主,up主发布新动态时便会通知所有关注他的用户

优点:使用该模式后,会以集合的方式来管理用户(Observer),包括注册,移除和通知

⑱中介者模式(Mediator)/减少子系统之间的相互调用

注意和外观模式进行区分

下定义:用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。

⑲备忘录模式(Memento)

下定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将这个对象恢复到原先保存的状态(游戏的存档机制???)

⑳解释器模式(Interpreter)/看到文法我就蚌埠煮了

下定义:给一个语言表达式,定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)

Context:环境角色(上下文),含有解释器之外的全局信息

AbstractExpression:抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有节点共享

TerminalExpression:终结符表达式,实现文法中的终结符相关的解释操作

NonTerminalExpression:非终结符表达式,实现文法中的非终结符相关的解释操作

②①状态模式(State)

下定义:它主要解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。当一个对象的状态发生变化时,允许改变其行为,这个对象看起来像是改变了其类

Context:维护state实例,这个实例定义当前实例

state:定义一个接口封装与Context的一个特点接口相关行为

concreteState:每个子类实现一个与context的一个状态的相关行为

②②策略模式(Strategy)

下定义:定义算法族,分别封装起来,让他们之间可以相互替换,此模式让算法的变化独立于使用算法的用户

该算法题体现了几个设计原则,第一,把变化的代码从不变的代码分离开来;第二,针对接口编程而不是具体类;第三,多用组合/聚合,少用继承

注意和桥接模式进行区分

②③职责链模式(Chain Of Responsibility)

下定义:责任链模式,为请求创建一个接受者对象的链,这种模式对请求的发送者和接受者进行解耦

该模式通常每个接收者都包含对另一个接收者的引用,如果一个对象不能处理该请求,那么它会把相同的请求传送给下一个接收者,以此类推…


最佳应用场景:多个对象处理同一个请求,比如多级请求、请假/加薪、易班请假等审批流程。再比如javaWeb的拦截器

未完待续…

现在只是简单理解了这些设计模式的基本思想,自身的思考还是不够。当然也有些模式还是一知半解。希望在后续的学习中能够有一些新的见解…嗯,先这样吧…