Java设计模式笔记——七个结构型模式

本文详细介绍了Java中的几种创建型设计模式,包括适配器模式的概念、类适配器和对象适配器,桥接模式的原理和案例,组合模式的用途和实现,装饰器模式的灵活性以及外观模式在简化系统交互中的作用,还有享元模式在资源优化方面的应用和代理模式的静态与动态代理方式。这些模式在软件设计中都有重要价值,能提高代码的灵活性和可扩展性。
摘要由CSDN通过智能技术生成

系列文章目录

第一章 Java设计模式笔记——七大设计原则
第二章 Java设计模式笔记——六个创建型模式



一、适配器模式

1.概念

假如我们去欧洲国家旅游电脑没电了,发现插座用的是两孔的(欧标),我们这时需要买个多功能转换插头 (适配器) ,这样就可以充电了。

在这里插入图片描述
与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

在这里插入图片描述

  • Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
  • Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心。在类适配器中,它通过实现Target并继承一个Adaptee对象使二者产生联系,在对象适配器中是通过关联一个Adaptee对象使二者产生联系。
  • Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

2.类适配器

以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter,220V 交流电相当于被适配者,我们的目标是 5V 直流

在这里插入图片描述

public interface Voltage5V {
    int output5v();
}
public class Voltage220V {
    public int output220V(){
        System.out.println("被适配者输出:"  + 220 + "V");
        return 220;
    }
}
public class VoltageAdpter extends Voltage220V implements Voltage5V{
    @Override
    public int output5v() {
        int output220V = output220V();
        System.out.println("适配者将220V转换为5V输出");
        return output220V / 44;
    }
}
public class Client {
    public static void main(String[] args) {
        Voltage5V voltage5V = new VoltageAdpter();
        System.out.println("转换后输出:" + voltage5V.output5v() + "V");
    }
}

在这里插入图片描述

总结

  1. Java 是单继承机制,所以Adapter(适配器类)需要继承 Adaptee(被适配类)这一点算是一个缺点, 因为这要求 Target(目标抽象类) 必须是接口,有一定局限性
  2. Adaptee 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。
  3. 由于其继承了 Adaptee 类,所以它可以根据需求重写 Adaptee 类的方法,使得 Adapter 的灵活性增

3.对象适配

根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系。我们将上述改为对象适配,只需将 Adapter 做修改,将Adaptee 类聚合到 Adapter 就行了。

在这里插入图片描述

public class VoltageObjectAdpter implements Voltage5V{
    private Voltage220V voltage220V;

    public Voltage220V getVoltage220V() {
        return voltage220V;
    }

    public void setVoltage220V(Voltage220V voltage220V) {
        this.voltage220V = voltage220V;
    }

    @Override
    public int output5v() {
        int output220V = voltage220V.output220V();
        System.out.println("适配者将220V转换为5V输出");
        return output220V / 44;
    }
}

在这里插入图片描述

4.缺省适配器模式

缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。

在这里插入图片描述

  • ServiceInterface(适配者接口):它是一个接口,通常在该接口中声明了大量的方法。
  • AbstractServiceClass(缺省适配器类):它是缺省适配器模式的核心类,使用空方法的形式实现了在ServiceInterface接口中声明的方法。通常将它定义为抽象类,因为对它进行实例化没有任何意义。
  • ConcreteServiceClass(具体业务类):它是缺省适配器类的子类,在没有引入适配器之前,它需要实现适配者接口,因此需要实现在适配者接口中定义的所有方法,而对于一些无须使用的方法也不得不提供空实现。在有了缺省适配器之后,可以直接继承该适配器类,根据需要有选择性地覆盖在适配器类中定义的方法。

二、桥接模式

1.概念

桥接模式是一种很实用的结构型设计模式,如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则”。与多层继承方案不同,它将两个独立变化的维度设计为两个独立的继承等级结构,并且在抽象层建立一个抽象关联,该关联关系类似一条连接两个独立继承结构的桥,故名桥接模式。

桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

在这里插入图片描述

  • Abstraction(抽象类):用于定义抽象类的接口,它一般是抽象类而不是接口,其中定义了一个Implementor(实现类接口)类型的对象并可以维护该对象,它与Implementor之间具有关联关系,它既可以包含抽象业务方法,也可以包含具体业务方法。
  • RefinedAbstraction(扩充抽象类):扩充由Abstraction定义的接口,通常情况下它不再是抽象类而是具体类,它实现了在Abstraction中声明的抽象业务方法,在RefinedAbstraction中可以调用在Implementor中定义的业务方法。
  • Implementor(实现类接口):定义实现类的接口,这个接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同,一般而言,Implementor接口仅提供基本操作,而Abstraction定义的接口可能会做更多更复杂的操作。Implementor接口对这些基本操作进行了声明,而具体实现交给其子类。通过关联关系,在Abstraction中不仅拥有自己的方法,还可以调用到Implementor中定义的方法,使用关联关系来替代继承关系。
  • ConcreteImplementor(具体实现类):具体实现Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同实现,在程序运行时,ConcreteImplementor对象将替换其父类对象,提供给抽象类具体的业务操作方法。

2.案例

现有各种型号的毛笔进行绘画,在绘画中需要着不同颜色,设计如下:
在这里插入图片描述

问题

  1. 扩展性问题(类爆炸),如果我们再增加毛笔大小,就需要增加各个颜色毛笔的类,同样如果我们增加一种颜色,也要在各个型号毛笔类下增加。
  2. 违反了单一职责原则,当我们增加毛笔型号时,要同时增加所有颜色的毛笔,这样增加了代码维护成本

3.改进

在这里插入图片描述

public interface Color {
    void coloring();
}
public class RedColor implements Color{
    @Override
    public void coloring() {
        System.out.println("开始着红颜色");
    }
}
public class BuleColor implements Color{
    @Override
    public void coloring() {
        System.out.println("开始着蓝颜色");
    }
}
public abstract class Pen {
    protected Color color;

    public void setColor(Color color) {
        this.color = color;
    }

    public abstract void drawing();
}
public class BigPen extends Pen{

    @Override
    public void drawing() {
        System.out.println("大号毛笔开始绘画");
        color.coloring();
    }
}
public class SmallPen extends Pen{
    @Override
    public void drawing() {
        System.out.println("小号毛笔开始绘画");
        color.coloring();
    }
}
public class Client {
    public static void main(String[] args) {
        Pen bigPen = new BigPen();
        bigPen.setColor(new RedColor());
        bigPen.drawing();
    }
}

在这里插入图片描述

桥接模式的主要优点如下:

  1. 分离抽象接口及其实现部分。桥接模式使用“对象间的关联关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便任何组合子类,从而获得多维度组合对象。
  2. 在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
  3. 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。

桥接模式的主要缺点:

  1. 桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。
  2. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。

在以下情况下可以考虑使用桥接模式:

  1. 如果一个系统需要在抽象化和具体化之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  2. “抽象部分”和“实现部分”可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。
  3. 一个类存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展。
  4. 对于那些不希望使用继承或因为多层继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

三、组合模式

1.概念

对于树形结构,当容器对象(如文件夹)的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象)并调用执行,牵一而动百,其中使用了递归调用的机制来对整个结构进行处理。由于容器对象和叶子对象在功能上的区别,在使用这些对象的代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下我们希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。组合模式为解决此类问题而诞生,它可以让叶子对象和容器对象的使用具有一致性。

组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。

在这里插入图片描述

  • Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
  • Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
  • Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。

2.案例

为了让系统具有更好的灵活性和可扩展性,我们设计一个文件夹的展示功能如下:

在这里插入图片描述

public abstract class AbstractFile {
    protected abstract void add(AbstractFile abstractFile);
    protected abstract void remove(AbstractFile abstractFile);
    protected abstract void print();
}
public class ImageFile extends AbstractFile{
    private String name;

    public ImageFile(String name) {
        this.name = name;
    }

    @Override
    protected void add(AbstractFile abstractFile) {
        System.out.println("不支持该操作");
    }

    @Override
    protected void remove(AbstractFile abstractFile) {
        System.out.println("不支持该操作");
    }

    @Override
    protected void print() {
        System.out.println("图片名称:" + name + ".jpg");
    }
}
public class TextFile extends AbstractFile{
    private String name;

    public TextFile(String name) {
        this.name = name;
    }

    @Override
    protected void add(AbstractFile abstractFile) {
        System.out.println("不支持该操作");
    }

    @Override
    protected void remove(AbstractFile abstractFile) {
        System.out.println("不支持该操作");
    }

    @Override
    protected void print() {
        System.out.println("TxT名称:" + name + ".jpg");
    }
}
public class Folder extends AbstractFile{
    private List<AbstractFile> abstractFileList = new ArrayList<>();
    private String name;

    public Folder(String name) {
        this.name = name;
    }

    @Override
    protected void add(AbstractFile abstractFile) {
        abstractFileList.add(abstractFile);
    }

    @Override
    protected void remove(AbstractFile abstractFile) {
        abstractFileList.remove(abstractFile);
    }

    @Override
    protected void print() {
        System.out.println("****文件夹名称:" + name + "****");
        for (AbstractFile abstractFile : abstractFileList){
            abstractFile.print();
        }
    }
}
public class Client {
    public static void main(String[] args) {
        AbstractFile computer = new Folder("电脑文件夹");
        AbstractFile txts = new Folder("图片文件夹");
        AbstractFile imgs = new Folder("txt文件夹");

        AbstractFile img1 = new ImageFile("香蕉图片");
        AbstractFile img2 = new ImageFile("苹果图片");

        AbstractFile txt1 = new TextFile("记录文档");
        AbstractFile txt2 = new TextFile("笔记文档");

        computer.add(txts);
        computer.add(imgs);

        imgs.add(img1);
        imgs.add(img2);

        txts.add(txt1);
        txts.add(txt2);

        computer.print();
        //imgs.print();
    }
}

在这里插入图片描述

组合模式的主要优点如下:

  1. 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  2. 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
  3. 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
  4. 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

组合模式的主要缺点如下:
在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。

适用场景:

  1. 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
  2. 在一个使用面向对象语言开发的系统中需要处理一个树形结构。
  3. 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。

四、装饰者模式

1.概念

装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,在现实生活中,这种情况也到处存在,例如一张照片,我们可以不改变照片本身,给它增加一个相框,使得它具有防潮的功能,而且用户可以根据需要给它增加不同类型的相框,甚至可以在一个小相框的外面再套一个大相框。

装饰模式是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入了装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。

在这里插入图片描述

  1. Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
  2. ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)
  3. Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
  4. ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

2.案例

现在有一个新的房子,我们需要给房子刷墙、贴砖等,用装饰者模式设计实现如下:
在这里插入图片描述

public abstract class House {
    protected abstract void display();
}
public class Apartment extends House{
    @Override
    protected void display() {
        System.out.println("别墅房子展示");
    }
}
public class Decorator extends House{
    private House house;

    public Decorator(House house) {
        this.house = house;
    }

    @Override
    protected void display() {
        house.display();
    }
}
public class WallDecorator extends Decorator{
    public WallDecorator(House house) {
        super(house);
    }

    @Override
    protected void display() {
        super.display();
        painting();
    }

    public void painting(){
        System.out.println("刷墙开始");
    }

}
public class Client {
    public static void main(String[] args) {
        House apartment = new Apartment();
        House wallDecorator = new WallDecorator(apartment);
        wallDecorator.display();
    }
}

在这里插入图片描述
装饰模式的主要优点如下:

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
  2. 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
  3. 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
  4. 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。

装饰模式的主要缺点如下:

  1. 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
  2. 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。

五、外观模式

1.概念

在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现,由于涉及到的类比较多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。外观模式通过引入一个新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。

外观模式:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

在这里插入图片描述

  • Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。
  • SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。

案例

个人在家生活:每次都需要打开灯、打开电视、打开空调;睡觉时关闭灯、关闭电视、关闭空调;操作起来都比较麻烦。我们可以加入一个智能小爱同学,帮助我们来管理这些家居。

在这里插入图片描述

public class Air {
    public void turnOn(){
        System.out.println("打开空调...");
    }

    public void turnOff(){
        System.out.println("关闭空调...");
    }
}
public class Light {
    public void turnOn(){
        System.out.println("打开电灯...");
    }

    public void turnOff(){
        System.out.println("关闭电灯...");
    }
}
public class TV {
    public void turnOn(){
        System.out.println("打开电视...");
    }

    public void turnOff(){
        System.out.println("关闭电视...");
    }
}
public class XiaoAiFacade {
    private Air air;
    private Light light;
    private TV tv;

    public XiaoAiFacade(Air air, Light light, TV tv) {
        this.air = air;
        this.light = light;
        this.tv = tv;
    }

    public void sleep(){
        System.out.println("小爱同学,我要睡觉了");
        air.turnOff();
        light.turnOff();
        tv.turnOff();
    }

    public void getUp(){
        System.out.println("小爱同学,我起床了");
        air.turnOn();
        light.turnOn();
        tv.turnOn();
    }
}
public class Client {
    public static void main(String[] args) {
        XiaoAiFacade xiaoAiFacade = new XiaoAiFacade(new Air(), new Light(), new TV());
        xiaoAiFacade.sleep();
        xiaoAiFacade.getUp();
    }
}

在这里插入图片描述

外观模式的主要优点如下:

  1. 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
  2. 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
  3. 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

外观模式的主要缺点如下:

  1. 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活 性。
  2. 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。

模式适用场景:

  1. 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
  2. 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
  3. 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

六、享元模式

1.概念

在软件系统中,有时候也会存在资源浪费的情况,例如在计算机内存中存储了多个完全相同或者非常相似的对象,如果这些对象的数量太多将导致系统运行代价过高,内存属于计算机的“稀缺资源”,不应该用来“随便浪费”,享元模式可以用于节约内存使用空间,实现对这些相同或者相似对象的共享访问。享元模式经典的应用场景就是池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

在这里插入图片描述

  • Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。

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

  1. 内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变
  2. 外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。

2.案例

为了节约存储空间,提高系统性能,使用享元模式来设计围棋软件中的棋子,结构如下所示:
在这里插入图片描述

public abstract class IgoChessman {
    public abstract String getColor();
    public void display(Coordinates coord){
        System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coord.getX() + "," + coord.getY() );
    }
}
public class Coordinates {
    private int x;
    private int y;

    public Coordinates(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}
public class BlackIgoChessman extends IgoChessman{
    @Override
    public String getColor() {
        return "黑色";
    }
}
public class WhiteIgoChessman extends IgoChessman{
    @Override
    public String getColor() {
        return "白色";
    }
}
public class IgoChessmanFactory {
    private static IgoChessmanFactory igoChessmanFactory = new IgoChessmanFactory();
    private static Map<String, IgoChessman> map;

    private IgoChessmanFactory() {
        map = new HashMap<>();
        map.put("b", new BlackIgoChessman());
        map.put("w", new WhiteIgoChessman());
    }

    public static IgoChessmanFactory getInstance(){
        return igoChessmanFactory;
    }

    public IgoChessman getIgoChessman(String color) {
        return map.get(color);
    }
}
public class Client {
    public static void main(String[] args) {
        IgoChessmanFactory igoChessmanFactory = IgoChessmanFactory.getInstance();

        //生成两颗黑子
        IgoChessman black1 = igoChessmanFactory.getIgoChessman("b");
        IgoChessman black2 = igoChessmanFactory.getIgoChessman("b");
        //生成两颗白子
        IgoChessman white1 = igoChessmanFactory.getIgoChessman("w");
        IgoChessman white2 = igoChessmanFactory.getIgoChessman("w");

        //显示棋子,同时设置位置
        black1.display(new Coordinates(1, 2));
        black2.display(new Coordinates(3, 4));
        white1.display(new Coordinates(5, 6));
        white2.display(new Coordinates(7, 8));
    }
}

在这里插入图片描述
享元模式的主要优点如下:

  1. 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
  2. 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

享元模式的主要缺点如下:

  1. 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  2. 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。

在以下情况下可以考虑使用享元模式:

  1. 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
  2. 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  3. 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

七、代理模式

1.概念

在软件开发中,也有一种设计模式可以提供与代购网站类似的功能。由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。

代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

在这里插入图片描述

代理模式有不同的形式, 主要有三种静态代理、动态代理 (JDK 代理、接口代理)和 Cglib 代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。

2.静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。

我们这里实现一个类似ORM框架的一个功能,结构如下:

在这里插入图片描述

public interface ITeacherDao {
    void teach();
}
public class TeacherDao implements ITeacherDao{
    @Override
    public void teach() {
        System.out.println("老师开始教学");
    }
}
public class TeacherDaoProxy implements ITeacherDao{
    private ITeacherDao iTeacherDao;

    public TeacherDaoProxy(ITeacherDao iTeacherDao) {
        this.iTeacherDao = iTeacherDao;
    }

    @Override
    public void teach() {
        System.out.println("开始代理....");
        iTeacherDao.teach();
        System.out.println("结束代理....");
    }
}
public class Client {
    public static void main(String[] args) {
        ITeacherDao iTeacherDao = new TeacherDaoProxy(new TeacherDao());
        iTeacherDao.teach();
    }
}

在这里插入图片描述

  • 优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展
  • 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护

3.动态代理

代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理;代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象;动态代理也叫做:JDK 代理、接口代。

在这里插入图片描述

public interface ITeacherDao {
    void teach();
}
public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("老师开始教学");
    }
}
public class ProxyFactory {
    private Object target; //目标对象

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("JDK 代理开始");
                return method.invoke(target, args);
            }
        });
    }
}
public class Client {
    public static void main(String[] args) {
        ITeacherDao iTeacherDao = (ITeacherDao) new ProxyFactory(new TeacherDao()).getProxyInstance();
        iTeacherDao.teach();
    }
}

在这里插入图片描述

4.Cglib代理

静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理,这就是 Cglib 代理。

Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有的也将Cglib代理归属到动态代理。

Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类。

在这里插入图片描述

public class TeacherDao {
    public void teach() {
        System.out.println("老师开始教学");
    }
}
public class ProxyFactory implements MethodInterceptor {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        //1. 创建一个工具类
        Enhancer enhancer = new Enhancer();
        //2. 设置父类
        enhancer.setSuperclass(target.getClass());
        //3. 设置回调函数
        enhancer.setCallback(this);
        //4. 创建子类对象,即代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib 代理开始...");
        Object val = method.invoke(target, objects);
        System.out.println("cglib 代理结束...");
        return val;
    }
}
public class Client {
    public static void main(String[] args) {
        TeacherDao teacherDao = (TeacherDao) new ProxyFactory(new TeacherDao()).getProxyInstance();
        teacherDao.teach();
    }
}

在这里插入图片描述

5.常用的几种代理

  1. 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。
  2. 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  3. 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  4. 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  5. 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

总结

以上就是几种创建型模式:适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式,代理模式,通过本文学习我们能快速掌握这几种模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值