设计模式(五)

装饰者模式

星巴克咖啡订单项目

星巴克咖啡订单项目(咖啡馆):

  1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式 咖啡)、Decaf(无因咖啡)

  2. 调料:Milk、Soy(豆浆)、Chocolate

  3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便

  4. 使用OO的来计算不同种类咖啡的费用: 客户可以点单品咖啡,也可以单品咖 啡+调料组合。

  5. Drink 是一个抽象类,表示饮料,des就是对咖啡的描述, 比如咖啡的名字,cost() 方法就是计算费用,Drink 类中做成一个抽象方法.

  6. Decaf 就是单品咖啡, 继承Drink, 并实现cost

  7. Espress && Milk 就是单品咖啡+调料,这个组合很多

  8. 问题:这样设计,会有很多类,当我们增加一个单品咖啡,或者一个新的调料, 类的数量就会倍增,就会出现类爆炸

装饰者模式原理

装饰者模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。这里提到的动态的将新功能附加到对象和ocp原则,在后面的应用实例上会以代 码的形式体现。

  1. 装饰者模式就像打包一个快递

主体:比如:陶瓷、衣服 (Component) // 被装饰者;包装:比如:报纸填充、塑料泡沫、纸板、木板(Decorator) 。Component 主体:比如类似前面的Drink,ConcreteComponent和Decorator ConcreteComponent:具体的主体, 比如前面的各个单品咖啡。Decorator: 装饰者,比如各调料.。Component与ConcreteComponent之间,如果 ConcreteComponent类很多,还可以设计一个缓冲层,将共有的部分提取出来, 抽象层一个类。

//抽象类
public abstract class Drink {
    private String des;//描述
    private float price;//价格

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    public abstract float cost();//计算价格
}
//被装饰者
public abstract class Coffee extends Drink{

    @Override
    public float cost() {
        return super.getPrice();
    }
}
public class Espresso extends Coffee {
    public Espresso() {
        setDes("意大利咖啡");
        setPrice(6.0f);
    }
}
public class LongBlack extends Coffee {
    public LongBlack() {
        setDes("美式咖啡");
        setPrice(5.0f);
    }
}
public class ShortBlack extends Coffee {
    public ShortBlack() {
        setDes("ShortBlack");
        setPrice(4.0f);
    }
}
//装饰者
public class Decorator extends Drink{
    //聚合Drink
    private Drink drink;

    public Decorator(Drink drink) {
        this.drink = drink;
    }

    @Override
    public float cost() {
        return getPrice() + drink.cost();
    }

    @Override
    public String getDes() {
        return super.getDes() + "&&" + drink.getDes();
    }
}
public class Milk extends Decorator {
    public Milk(Drink drink) {
        super(drink);
        setDes("牛奶");
        setPrice(2.0f);
    }
}
public class Soy extends Decorator {
    public Soy(Drink drink) {
        super(drink);
        setDes("豆浆");
        setPrice(1.5f);
    }
}
public class Chocolate extends Decorator {
    public Chocolate(Drink drink) {
        super(drink);
        setDes("巧克力");
        setPrice(3.0f);
    }

}
//客户端测试
Drink drink = new Espresso();
System.out.println("drink价格: " + drink.cost());
System.out.println("drink描述:" + drink.getDes());

drink = new Milk(drink);
System.out.println("drink价格: " + drink.cost());
System.out.println("drink描述:" + drink.getDes());

drink = new Chocolate(drink);
System.out.println("drink价格: " + drink.cost());
System.out.println("drink描述:" + drink.getDes());

drink = new Chocolate(drink);
System.out.println("drink价格: " + drink.cost());
System.out.println("drink描述:" + drink.getDes());

在JDK源码中使用

Java的IO结构,FilterInputStream就是一个装饰者

InputStream//是一个抽象类,即Component
FilterInputStream extends InputStream { //是一个装饰者类Decorator protected volatile InputStream in //被装饰的对象 DataInputStream extends FilterInputStream implements DataInput { //FilterInputStream子类

组合模式

学校院系展示需求

编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系 组成,一个学校有多个学院,一个学院有多个系。如图:

传统方案解决学校院系展示

传统方案解决学校院系展示存在的问题分析

  1. 将学院看做是学校的子类,系是学院的子类,这样实际上是站在组织大小来进行分层次的
  2. 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个系, 因此这种方案,不能很好实现的管理的操作,比如对学院、系的添加,删除,遍历等
  3. 解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作。 => 组合模式

组合模式基本介绍

  1. 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
  2. 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
  3. 这种类型的设计模式属于结构型模式。
  4. 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component 子部件, Component 可以是抽象类或者接口 。Leaf : 在组合中表示叶子节点,叶子节点没有子节点。

解决的问题

组合模式解决这样的问题,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,它能够提供一致的方式,而不用考虑它是节点还是叶子,对应的示意图

代码实现

//抽象类Component
public abstract class OrganizationComponent {
    private String name;
    private String desc;

    public OrganizationComponent(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    //增
    public void add(OrganizationComponent o){
        throw new UnsupportedOperationException();
    }
    //删
    public void remove(OrganizationComponent o){
        throw new UnsupportedOperationException();
    }
    //遍历
    public abstract void list();
}
//非叶子节点Composite
//大学
public class University extends OrganizationComponent {
    private List<OrganizationComponent> organizationComponents = new ArrayList<>();
    public University(String name, String desc) {
        super(name, desc);
    }

    @Override
    public void add(OrganizationComponent o) {
        organizationComponents.add(o);
    }

    @Override
    public void remove(OrganizationComponent o) {
        organizationComponents.remove(o);
    }

    @Override
    public void list() {
        System.out.println("--------"+getName()+"--------");
        for(OrganizationComponent o : organizationComponents){
            o.list();
        }
    }
}
//学院
public class College extends OrganizationComponent {
    private List<OrganizationComponent> organizationComponents = new ArrayList<>();
    public College(String name, String desc) {
        super(name, desc);
    }

    @Override
    public void add(OrganizationComponent o) {
        organizationComponents.add(o);
    }

    @Override
    public void remove(OrganizationComponent o) {
        organizationComponents.remove(o);
    }

    @Override
    public void list() {
        System.out.println("----" + getName() + "----");
        for(OrganizationComponent o : organizationComponents){
            o.list();
        }
    }
}
//叶子节点,专业
public class Department extends OrganizationComponent {
    public Department(String name, String desc) {
        super(name, desc);
    }
    @Override
    public void list() {
        System.out.println(getName() + " " + getDesc());
    }
}

组合模式的注意事项和细节

  1. 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子 的问题。
  2. 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系, 客户端不用做出任何改动.
  3. 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
  4. 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
  5. 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

外观模式

影院管理项目

组建一个家庭影院: DVD播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为:

• 直接用遥控器:统筹各设备开关

• 开爆米花机

• 放下屏幕

• 开投影仪

• 开音响

• 开DVD,选dvd

• 去拿爆米花

• 调暗灯光

• 播放

• 观影结束后,关闭各种设备

传统方式解决影院管理

传统方式解决影院管理问题分析

  1. 在ClientTest 的main方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程混乱,没有清晰的过程
  2. 不利于在ClientTest 中,去维护对子系统的操作
  3. 解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法 ready, play, pause, end ),用来访问子系统中的一群接口
  4. 也就是说就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节, 使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节 => 外观模式

代码演示

//子系统模块
public class DVDPlayer {
    private DVDPlayer(){};
    private static final DVDPlayer instance = new DVDPlayer();
    public static DVDPlayer getInstance(){
        return instance;
    }

    //开
    public void on(){
        System.out.println("dvd on");
    }
    //关
    public void off(){
        System.out.println("dvd off");
    }
    //播放
    public void play(){
        System.out.println("dvd is playing");
    }
    //暂停
    public void pause(){
        System.out.println("dvd pause");
    }
}
public class Popcorn {
    private Popcorn(){};
    private static final Popcorn instance = new Popcorn();
    public static Popcorn getInstance(){
        return instance;
    }
    public void on(){
        System.out.println("popcorn on");
    }
    public void off(){
        System.out.println("popcorn off");
    }
    public void pop(){
        System.out.println("popcorn pop");
    }
}
public class Projector {
    private Projector(){};
    private static final Projector instance = new Projector();
    public static Projector getInstance(){
        return instance;
    }
    public void on(){
        System.out.println("projector on");
    }
    public void off(){
        System.out.println("projector off");
    }
    public void focus(){
        System.out.println("projector focus");
    }
}
public class Screen {
    private Screen(){};
    private static final Screen instance = new Screen();
    public static Screen getInstance(){
        return instance;
    }
    public void down(){
        System.out.println("screen down");
    }
    public void up(){
        System.out.println("screen up");
    }
}
public class Stereo {
    private Stereo(){};
    private static final Stereo instance = new Stereo();
    public static Stereo getInstance(){
        return instance;
    }
    public void on(){
        System.out.println("stereo on");
    }
    public void off(){
        System.out.println("stereo off");
    }
    public void up(){
        System.out.println("stereo up");
    }
}
public class TheaterLight {
    private TheaterLight(){};
    private static final TheaterLight instance = new TheaterLight();
    public static TheaterLight getInstance(){
        return instance;
    }
    public void off(){
        System.out.println("theaterLight off");
    }
    public void on(){
        System.out.println("theaterLight on");
    }
    public void dim(){
        System.out.println("theaterLight dim");
    }
    public void bright(){
        System.out.println("theaterLight bright");
    }
}
//外观类
public class HomeTheaterFacade {
    private DVDPlayer dvdPlayer;
    private Popcorn popcorn;
    private Projector projector;
    private Screen screen;
    private Stereo stereo;
    private TheaterLight theaterLight;

    public HomeTheaterFacade() {
        dvdPlayer = DVDPlayer.getInstance();
        popcorn = Popcorn.getInstance();
        projector = Projector.getInstance();
        screen = Screen.getInstance();
        stereo = Stereo.getInstance();
        theaterLight = TheaterLight.getInstance();
    }

    public void ready(){
        dvdPlayer.on();
        popcorn.on();
        popcorn.pop();
        projector.on();
        screen.down();
        stereo.on();
        theaterLight.dim();
    }
    public void play(){
        projector.focus();
        dvdPlayer.play();
    }
    public void pause(){
        dvdPlayer.pause();
        theaterLight.bright();
    }

    public void end(){
        theaterLight.bright();
        stereo.off();
        screen.up();
        projector.off();
        popcorn.off();
        dvdPlayer.off();
    }
}
//客户端
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.end();

基本介绍

外观模式(Facade),也叫“过程模式:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部细节。

外观模式的注意事项和细节

  1. 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
  2. 外观模式对客户端与子系统的耦合关系,让子系统内部的模块更易维护和扩展
  3. 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
  4. 当系统需要进行分层设计时,可以考虑使用Facade模式
  5. 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口, 让新系统与Facade类交互,提高复用性
  6. 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。 要以让系统有层次,利于维护为目的。

享元模式

展示网站项目需求

小型的外包项目,给客户A做一个产品展示网站,客户A的朋友感觉效果不错,也希望做这样的产品展示网站,但是要求都有些不同:

  1. 有客户要求以新闻的形式发布
  2. 有客户人要求以博客的形式发布
  3. 有客户希望以微信公众号的形式发布

传统解决方法

传统方案解决网站展现项目-问题分析

  1. 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费
  2. 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、 数据库空间等服务器资源都可以达成共享,减少服务器资源
  3. 对于代码来说,由于是一份实例,维护和扩展都更加容易
  4. 上面的解决思路就可以使用享元模式来解决

享元模式基本介绍

享元模式(Flyweight Pattern)也叫 蝇量模式: 运用共享技术有效地支持大量细粒度的对象。常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个。享元模式能够解决重复对象的内存浪费的问题, 当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式。

享元模式的原理类图

  1. FlyWeight 是抽象的享元角色, 他是产品的抽象类, 同时定义出对象的外部状态和内部状态(后面介绍) 的接口或实现
  2. ConcreteFlyWeight 是具体的享元角色,是具体的产品类,实现抽象角色定义相关业务
  3. UnSharedConcreteFlyWeight 是不可共享的角色,一般不会出现在享元工厂
  4. FlyweightFactory是享元工厂

内部状态和外部状态

比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一 点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后, 落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态

享元模式提出了两个要求:细粒度和共享对象。这里就涉及到内部状态和外部状态 了,即将对象的信息分为两个部分:内部状态和外部状态

内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变

外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。

举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解 决了对象的开销问题。

代码实现

//享元模式中的抽象类
public abstract class WebSite {
    //使用网站
    public abstract void use(User user);
}
//外部状态,不同的使用者
public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User(String name) {
        this.name = name;
    }
}
//具体网站类型
public class ConcreteWebSite extends WebSite {
    private String type;//网站类型内部状态

    public ConcreteWebSite(String type) {
        this.type = type;
    }

    @Override
    public void use(User user) {//User使用者外部状态
        System.out.println("网站类型: " + type + "正在使用... 使用者:" + user.getName());
    }
}
//享元工厂
public class FlyweightFactory {
    //使用容器存储所有不同类型的网站,池
    private HashMap<String,ConcreteWebSite> pool = new HashMap<>();
    public WebSite getWebSite(String type){
        if(!pool.containsKey(type)){
            //当没有包含该网站类型的时候创建网站类型并放入池中
            pool.put(type,new ConcreteWebSite(type));
        }
        return pool.get(type);
    }

    public int getWebSiteCount(){
        return pool.size();
    }
}
//client
FlyweightFactory flyweightFactory = new FlyweightFactory();
WebSite webSite1 = flyweightFactory.getWebSite("网站");
webSite1.use(new User("tom"));
WebSite website2 = flyweightFactory.getWebSite("博客");
website2.use(new User("jack"));
website2.use(new User("smith"));
System.out.println(flyweightFactory.getWebSiteCount());

注意事项

  1. 在享元模式这样理解,“享”就表示共享,“元”表示对象
  2. 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
  3. 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储
  4. 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率
  5. 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方.
  6. 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制。
  7. 享元模式经典的应用场景是需要缓冲池的场景,比如 String常量池、数据库连接池。
posted @ 2021-11-03 19:19  无涯子wyz  阅读(86)  评论(0)    收藏  举报