设计模式总结:

设计模式总结:
第一章:面向对象设计原则:

  1. 单一职责原则:
    一个类只做一件事,因为当它承担的职责越多,它被复用的可能性就越小,而一个类承担的职责越多,相当于将这些职责耦合在一起,当其中一个职责发生变化的时候会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责分装在不同的类中,如果多个职责总是发生改变,则可将他们封装在同一类中。
    2.开闭原则
    简单一句话理解:对扩展开放,对修改关闭。
    实现方法:定义一个相对稳定的抽象层,而将不同的实现行为转到实现层中完成,方便复用与扩展。

  2. 里氏代换原则
    所有引用基类(父类)的地方必须能 透明地使用其子类的对象。
    子类可以扩展父类的功能,但不能改变父类原有的功能(子类可以有自己特有的方法,但是尽量不要去重写父类方法),不然代码出问题的几率大大增加。

  3. 依赖倒转原则
    高层模块不应该依赖低层模块,他们都应该依赖抽象,抽象不应该依赖细节,细节应该依赖抽象(就是要求针对接口编程,而不是针对实现编程)
    5.接口隔离原则
    根据接口隔离原则,当一个接口太大时需要将他分割成一些更细小的接口,使用该接口的客户端仅需指导与之相关的方法即可,每一个接口承担一种相对独立的角色,不干不该干的事,该干的事都要干。

6.合成复用原则
优先使用对象组合,而不是通过继承来达到复用的目的
7.迪米特法则
每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。(降低单位间通信的宽带与深度)
总结:
 总的理解这还是要实现高内聚低耦合,方便复用与扩展。
 开闭原则是面向对象的基石。
 七大原则之间并不是相互孤立的,彼此间存在着一定关联。

第二章:工厂模式
1)简单工厂模式:不利于产生系列产品;
2)工厂方法模式:又称为多形性工厂;
3)抽象工厂模式:又称为工具箱,产生产品族,但不利于产生新的产品;
这三种模式从上到下逐步抽象,并且更具一般性。如果我只是按照书面这么去表达可能你看过之后,现在暂时能记住,可能过两天或者面试的时候一紧张,估计就什么都忘了,下面我用通俗的例子来讲解一下。
简单工厂模式,就像你只有一个加工厂,这个工厂能够生产两种不同类型的产品(钢笔和铅笔,这两种笔都可以写字,只是写字这种行为不同而已),工厂有两条生产线,一条生产钢笔,一条生产铅笔。所以当你接到钢笔的订单时,你就生产钢笔,否则生产铅笔。此时你就只是一个小老板,随时可能面临倒闭。
工厂方法模式,就像你有两个加工厂,一家生产钢笔,一家生产铅笔,每家工厂有几条生产线就无所谓了。所以当你接到钢笔的订单时,你就去找钢笔的加工厂生产就行,否则铅笔加工厂。此时你有一定的抗风险能力了,算是一个中型的老板了。
抽象工厂模式,就相当于你是一个产品供应商(Provider),认识两家加工厂老板,每家加工厂都能生产钢笔和铅笔,只不过生产的钢笔和铅笔,品牌不一样。所以当你接到订单时,直接去找加工厂老板就行了。此时已经形成了供应商渠道了,你也走上了人生巅峰。

适配器模式
通过学习,发现适配器模式在生活中还是存在许多真实的例子:读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。还有:在插耳机时需要一个耳机转接器,才能插耳机。这个转接器就相当于适配器。
1、应用场景
• 实现已实现接口的复用,利用已有的代码实现功能。
• 使用已存在的部分子类,可选用随想适配器,适配子类的父类。
2、优缺点
优点:
• 更好的复用性。适配器模式可复用已实现接口的兼容。
• 更好的扩展性。实现适配器的过程中可以调用自己开发的功能,实现系统的扩展。
缺点:
过多使用适配器,系统会比较混乱,不易理解。
桥接模式
在此之前,我对于桥接模式的应用场景几乎没什么概念概念。感觉就主要是为了减少类的创建。
像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。
//公共的驱动接口
public interface Driver {
public void getConnection();
}
//第一个实现类 mysql驱动类
public class MysqlDriver implements Driver{
public void getConnection() {
System.out.println(“mysql 数据库连接”);
}
}
//第二个实现类 oracle驱动类
public class OracleDriver implements Driver {
public void getConnection() {
System.out.println(“oracle数据库连接”);
}
}
//抽象的管理器 Bridge
public abstract class Manager {
private Driver driver;
public void getConnection(){
driver.getConnection();
}
public void setDriver(Driver driver) {
this.driver = driver;
}
}
//具体的驱动管理器 Bridge
public class DriverManager extends Manager {
public DriverManager(Driver driver){
setDriver(driver);
}
public void getConnection() {
super.getConnection();
}
}

//测试类 注意我们的抽象和具体实现是分开的,无论他们如何变化都不会影响到我们bridge中的功能执行
//JDBC中,我们使用的就是一系列javaAPI提供的接口,而且数据公司商则给我们提供接口的实现
public class Test {
public static void main(String[] args) {

    DriverManager manager = new DriverManager(new MysqlDriver());
    manager.getConnection();

    manager = new DriverManager(new OracleDriver());
    manager.getConnection();
}

}

组合模式:
什么是组合模式?比如说某公司发了一个春节放假通知。只需要告知相关负责人即可。至于这个部门,包括一整个公司分部,项目部,研发部,还是说就负责人一个人,我么并不需要关心,这是负责人该关心的事情。事实上如果这个部门不止负责人一个人的话,他大概率也是转告下一层部门的负责人而已。这就是组合模式,又称部分整体模式,用于将一组相似的对象作为单一的对象整体,进而将部分与整体构造成树形结构。

透明模式是把组合使用的方法放到抽象类中,不管叶子对象还是树枝对象都有相同的结构这样做的好处就是叶子节点和树枝节点对于外界没有区别,它们具备完全一致的行为接口。

安全组合模式,在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法。这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。

既然组合模式 会被分为两种实现,那么肯定是不同的场合某一种会更加适合,也即具体情况具体分析。透明组合模式 将公共接口封装到抽象根节点中,那么系统所有节点就具备一致行为,所以如果当系统绝大多数层次具备相同的公共行为时,采用 透明组合模式 也许会更好(代价:为剩下少数层次节点引入不需要的方法);而如果当系统各个层次差异性行为较多或者树节点层次相对稳定(健壮)时,采用 安全组合模式

装饰模式
装饰器模式:在不改变原有的一个类的情况下,动态地扩展一个对象的功能。装饰器通过持有一个对象,将其包裹起来,并增强这个对象的功能。看上面的说明并不好理解,我们举个例子。比如平时我们吃的馒头,有各式各样的,比如普通的白馒头,还有加了糖的甜馒头,还有捏了各种造型的馒头,比如外形是小兔子馒头,小燕子馒头等。按照常规的实现,我们可能是先写一个馒头制作步骤的接口,各种不同馒头类去实现接口中的馒头制作方法,new哪种馒头,就制作哪种馒头。用装饰器模式后,我们做甜味馒头,其步骤和方法也是基于普通馒头的做法,稍加更改,就能完成甜味馒头的制作。所以我们制作甜味馒头,就让甜味馒头的制作类中持有一个普通馒头的对象,在需要更改步骤的方法中做修改,这样就相当于增强了原来普通馒头中某些步骤额功能,从而制作出新品种的馒头。
1、定义馒头制作接口
public interface ISteamedBun {

/**
 * 准备材料
 */
void prepare();

/**
 * 和面
 */
void kneadDough();

/**
 * 蒸馒头
 */
void steamed();

/**
 * 做馒头
 */
void make();

}

2、制作普通馒头

public class NormalSteamedBun implements ISteamedBun {

@Override
public void prepare() {
    System.out.println("准备食材:发面...");
}

@Override
public void kneadDough() {
    System.out.println("和面...");
}

@Override
public void steamed() {
    System.out.println("蒸馒头,出锅...");
}

@Override
public void make() {
    this.prepare();
    this.kneadDough();
    this.steamed();
}

}

3、制作甜味馒头,我们在和面的时候,加了糖,最后的馒头就是甜味的了
/**

  • @Auther: Jaylen.zhou

  • @Description:
    */
    public class SweetSteamedBun extends NormalSteamedBun {

    NormalSteamedBun steamedBun;

    public SweetSteamedBun(NormalSteamedBun steamedBun) {
    this.steamedBun = steamedBun;
    }

    /**

    • 加糖的步骤当然也可以放到发酵的阶段,个人觉得放在和面接阶段
      /
      @Override
      public void kneadDough() {
      System.out.println(“面粉中加糖,调甜味…”);
      steamedBun.kneadDough();
      }
      }
      4、制作小鸟造型馒头,这里也是一样,和完面,把馒头做出小鸟造型
      /
      *
  • @Auther: Jaylen.zhou

  • @Description:
    */
    public class BirdSteamedBun extends NormalSteamedBun {

    NormalSteamedBun steamedBun;

    public BirdSteamedBun(NormalSteamedBun steamedBun) {
    this.steamedBun = steamedBun;
    }

    @Override
    public void kneadDough() {
    steamedBun.kneadDough();
    System.out.println(“和面后,开始给馒头做成小鸟造型…”);
    }
    }

5、测试
/**

  • @Auther: Jaylen.zhou

  • @Description:
    */
    public class Client {

    public static void main(String[] args) {
    NormalSteamedBun normalSteamedBun = new NormalSteamedBun();

     System.out.println("------【甜味】馒头制作开始------");
     SweetSteamedBun sweetSteamedBun = new SweetSteamedBun(normalSteamedBun);
     sweetSteamedBun.make();
     System.out.println("------【甜味】馒头制作结束------");
    
     System.out.println("------小鸟造型【普通】馒头制作开始------");
     BirdSteamedBun normalBirdSteamedBun = new BirdSteamedBun(normalSteamedBun);
     normalBirdSteamedBun.make();
     System.out.println("------小鸟造型【普通】馒头制作结束------");
    
     System.out.println("------小鸟造型【甜味】馒头制作开始------");
     BirdSteamedBun birdSweetSteamedBun = new BirdSteamedBun(sweetSteamedBun);
     birdSweetSteamedBun.make();
     System.out.println("------小鸟造型【甜味】馒头制作结束------");
    

    }
    }

最后制作小鸟造型甜味馒头的方法,是制作小鸟馒头的类,持有了一个制作甜味馒头的对象,基于甜味馒头,加上小鸟馒头的制作方法,就制作出了小鸟造型甜味馒头。也就是我们可以任意组合各种馒头的对象,这样就能做出各种各样的馒头,这就是装饰器模式的威力。这就好像是积木一样,基于几块基本的木块,可以让你摆出各种造型。

总结:装饰器模式意在对一个对象的功能进行增强,通过持有一个对象,来增强其功能。在demo中,继承了普通馒头,就是为了能让各种馒头都作为对象,传入到另一种馒头制作类中,从而实现各种组合和装饰效果。

外观模式
外观模式:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式优点:
1、它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易
2、它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可
3、一个子系统的修改对其他子系统没有任何影响,而且子系统的内部变化也不会影响到外观对象
外观模式缺点:
1、不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性
2、如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。

享元模式

享元模式,主要作用就是用来共享,共享重复元素,从而达到节省内存的目标。
如果一个类被大量创建使用,并且这个对象本身的属性在使用方那里又不会被改变,那么这个对象就可以被共享,被任意多的类或对象拿去共享。
举例来说,如果让你实现一个五子棋游戏,当一方落一子,这时你是不是new一个黑子或白子,并记录位置呢,那一个棋盘棋子的颜色只有黑白两种,只是位置不同,这时候每个棋子都new一份,一盘棋下来,棋盘的棋子的颜色属性是重复的,就会造成内存的浪费。
这仅仅是一盘棋,如果你要实现一个五子棋游戏大厅,同一时间在线人数达到百万,棋子数可能达到千万甚至几亿数十亿颗,那么这个内存就非常可观了,优化内存也就是必须要做的了。
1、定义共享部分,这里也就是棋子的颜色部分是共享的,所以抽出来作为一个单独的类
public class ChessPieceFlyweight {

private ChessPieceColorEnum color;

public ChessPieceFlyweight(ChessPieceColorEnum color) {
    this.color = color;
}

public ChessPieceColorEnum getColor() {
    return color;
}

}

这里只有get颜色方法,没有set颜色方法,因为颜色是固定的,不应该被修改。
2、定义了棋子颜色枚举类,这里在代码中直接写“白”和“黑”也是可以的
public enum ChessPieceColorEnum {

RED,BLACK

}
3、定义共享部分的生产工厂,防止重复创建
public class ChessPieceFWFactory {

private static final Map ps = new HashMap();

static {
    ps.put(1,new ChessPieceFlyweight(ChessPieceColorEnum.RED));
    ps.put(2,new ChessPieceFlyweight(ChessPieceColorEnum.BLACK));
}

public static ChessPieceFlyweight getChessPieceFlyweight(int type) {
    return ps.get(type);
}

}
我们在类中使用静态代码块来实现白子和黑子,只创建一份,并放入map中。
4、棋子类
public class ChessPiece {

//棋子共享部分--颜色
private ChessPieceFlyweight chessPieceFlyweight;
//棋子坐标
private int positionX;
private int positionY;
public ChessPiece(ChessPieceFlyweight fw, int positionX, int positionY) {
    this.chessPieceFlyweight = fw;
    this.positionX = positionX;
    this.positionY = positionY;
}

@Override
public String toString() {
    String s = "positionX:" + this.positionX + "---this.positionY:" + this.positionY + "---棋子颜色:" + this.chessPieceFlyweight.getColor();
    return s;
}

}
棋子类中有棋子的X和Y坐标,并且有共享部分:棋子颜色。
棋子的颜色统一从工厂中获取的,全局只有一份,所以实现了节省内存。
5、测试
public class Client {

public static void main(String[] args) {
    for(int i=0; i<10; i++) {
        if(i%2 == 0) {
            ChessPiece chessPiece = new ChessPiece(ChessPieceFWFactory.getChessPieceFlyweight(1), i + 1, i + 1);
            System.out.println(chessPiece);
        } else {
            ChessPiece chessPiece = new ChessPiece(ChessPieceFWFactory.getChessPieceFlyweight(2), i + 1, i + 1);
            System.out.println(chessPiece);
        }
    }
}

}
输出:

享元模式的目标就是节省内存,如果有大量重复对象创建,并且需要节省内存,那么你就要考虑使用享元模式了。

代理模式
静态代理总结:

  1. 其实,所谓的静态代理,其实就是创建一个类【名为代理类】,然后该类实现了与目标类【业务层对象】一样的接口后,
    重写接口内容,里面做增强业务,也就是自定义操作,然后需要执行目标类的方法时,调用注入的目标类[即业务层对象]的方法即可,就是目标类外再套一层方法而已。
  2. 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。

动态代理总结:

  1. 动态代理又称为jdk代理、接口代理。
  2. 动态代理解决静态代理的缺点,代理类不需要再与目标对象实现一样的接口。
  3. 虽然代理类没有直接实现在目标类所实现的接口,但是在内存动态的创建代理对象 方法的注入参数需要 目标对象实现的接口的类型 ,
    因此目标类必须要最少实现一个接口 ,否则无法使用动态代理。
  4. 缺点:有些类不是业务层的,仅仅是一个单独的类 ,没有实现任何接口,是不能使用动态代理的。

示例:
动态代理系统的GetSelectNextFlowConditionCmd 修改标准的指定流转界面的参数:

职责链模式
职责链模式:将请求和处理进行拆分,从而实现了解耦,并且提高了系统的灵活性。
将上游和下游分裂开来,彼此之间消除了耦合。但是这也并不是完美的,它也有自己的缺点,一个很明显的缺点就是这样的效率会比较低。因为一层层链路传递都是需要时间的,尤其是这些链路如果还要远程调用的话,那么每一层转换都会有延迟以及相应时间,所以整个系统的效率可能会不高。
还有呢就是我们调试的时候可能会不太方便,错误不太容易排查。

命令模式

命令模式适用于要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个, 只需在程序运行时指定具体的请求接收者即可。
比如说我们模拟一下 电视机和机顶盒,开机功能的实现。就是简单的命令模式。
首先,我们要有“接收者”,这是接收命令,实现具体功能的。
public interface Receiver {
public void turnOn();
}

public class Tv implements Receiver {
@Override
public void turnOn() {
System.out.println(“电视开机”);
}
}

public class Stb implements Receiver {
@Override
public void turnOn() {
System.out.println(“机顶盒开机”);
}
}

接着,我们要定义一个开机命令来执行。比如说机顶盒和电视机都有不同的遥控器,分别发射不同的命令
public interface Command {
public void exec();
}

public class TurnOnCommand implements Command {
private Receiver receiver;
public TurnOnCommand(Receiver receiver) {
super();
this.receiver = receiver;
}

@Override
public void exec() {
    receiver.turnOn();
}

}

然后,我们要定义一个调用者,其实就是遥控器,由它来触发命令。
public class Invoker {
public void exeCommand(Command command) {
command.exec();
}
}

最后执行main函数
public class Client {

public static void main(String[] args) {
    Receiver Tv = new Tv();
    Receiver Stb = new Stb();

    Invoker invoker = new Invoker();

    Command turnOnTv = new TurnOnCommand(Tv);
    Command turnOnStb = new TurnOnCommand(Stb);

    invoker.exeCommand(turnOnTv);
    invoker.exeCommand(turnOnStb);

}

}

解释器模式
我们可以通过解释器将语言中的句子解析成一颗语法树,从而对其进行操作。所谓的语法树就是将句子通过文法进行推导,进而获取到句子的一颗树形表现形式。
一、使用场景
1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
2、一些重复的问题可以使用一种简单的语言进行表达。
3、语言的文法较为简单的时候可以考虑。

1、使用解释器模式可以提高代码的可扩展性。
2、使用解释器模式会引起类膨胀。
3、解释器模式会采用递归调用方法,可能会降低效率,并且会提高维护和调试的成本。
4、解释器模式的使用场景比较少,开发中也不经常接触到,一般常用在编译器、运算表达式计算、正则表达式解析等。

迭代器模式
迭代器模式在我们生活中应用场景也挺广泛的,比如物流系统中的传送带,不管传送的是什么物品,都被打包成一个一个的箱子并且有一个统一的二维码。这样我们不需要关心箱子里面是啥,我们在分发时只需要一个一个检发送的目的地即可。
迭代器模式在源码中的体现
从源码中,看到两个主要的方法定义hasNext()和next()方法,其实是在组合模式中我们见到过。迭代器模式和组合模式,两者似乎存在一定的相似性。组合模式解决的是统一树形结构各层次访问接囗,迭代器模式解决的是统一各集合对象元素遍历接囗。虽然他们的适配场景不同,但核心理念是相通的。在ArrayList类中的内部实现类Itr,它也是实现了Iterator接口的。
迭代器模式,提供一套标准,让用户无需关心具体的实现细节,用统一的方式给用户提供最便捷的服务,用户填好自己的收货地址、下完单付完款,就可以等收货了,无须关心是发什么快递。 这样就相当于提供最简单,最省心的服务。

你可能感兴趣的:(设计模式)