Java 中设计模式
  h9htfs4cnhmS 2023年12月31日 18 0

Java 中一般认为有23种设计模式,当然暂时不需要所有的都会,但是其中常见的几种设计模式应该去掌握。

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

创建型设计模式

1. 单例模式

所谓的单例设计指的是一个类只允许产生一个实例化对象。

最好理解的一种设计模式,分为懒汉式和饿汉式。

饿汉式:构造方法私有化,外部无法产生新的实例化对象,只能通过static方法取得实例化对象

class Singleton {
    /**
     * 在类的内部可以访问私有结构,所以可以在类的内部产生实例化对象
     */
    private static Singleton instance = new Singleton();
    /**
     * private 声明构造
     */
    private Singleton() {

    }
    /**
     * 返回对象实例
     */
    public static Singleton getInstance() {
        return instance;
    }

    public void print() {
        System.out.println("Hello Singleton...");
    }
}

懒汉式:当第一次去使用Singleton对象的时候才会为其产生实例化对象的操作

class Singleton {

    /**
     * 声明变量
     */
    private static volatile Singleton singleton = null;

    /**
     * 私有构造方法
     */
    private Singleton() {

    }

    /**
     * 提供对外方法
     * @return 
     */
    public static Singleton getInstance() {
        // 还未实例化
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    public void print() {
        System.out.println("Hello World");
    }
}

当多个线程并发执行 getInstance 方法时,懒汉式会存在线程安全问题,所以用到了 synchronized 来实现线程的同步,当一个线程获得锁的时候其他线程就只能在外等待其执行完毕。而饿汉式则不存在线程安全的问题。

2. 工厂设计模式

工厂模式分为工厂方法模式和抽象工厂模式。

工厂方法模式

工厂方法模式:

1. 工厂方法模式分为三种:普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。
2. 多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。
3. 静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

1. 普通工厂模式

建立一个工厂类,对实现了同一接口的一些类进行实例的创建。

interface Sender {
    void Send();
}

class MailSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is mail sender...");
    }
}

class SmsSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is sms sender...");
    }
}

public class FactoryPattern {
    public static void main(String[] args) {
        Sender sender = produce("mail");
        sender.Send();
    }
    public static Sender produce(String str) {
        if ("mail".equals(str)) {
            return new MailSender();
        } else if ("sms".equals(str)) {
            return new SmsSender();
        } else {
            System.out.println("输入错误...");
            return null;
        }
    }
}

2. 多个工厂方法模式

该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。

interface Sender {
    void Send();
}

class MailSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is mail sender...");
    }
}

class SmsSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is sms sender...");
    }
}

class SendFactory {
    public Sender produceMail() {
        return new MailSender();
    }

    public Sender produceSms() {
        return new SmsSender();
    }
}

public class FactoryPattern {
    public static void main(String[] args) {
        SendFactory factory = new SendFactory();
        Sender sender = factory.produceMail();
        sender.Send();
    }
}

3. 静态工厂方法模式

将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

interface Sender {
    void Send();
}

class MailSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is mail sender...");
    }
}

class SmsSender implements Sender {

    @Override
    public void Send() {
        System.out.println("This is sms sender...");
    }
}

class SendFactory {
    public static Sender produceMail() {
        return new MailSender();
    }

    public static Sender produceSms() {
        return new SmsSender();
    }
}

public class FactoryPattern {
    public static void main(String[] args) {
        Sender sender = SendFactory.produceMail();
        sender.Send();
    }
}

抽象工厂模式

工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要扩展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?

那么这就用到了抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

interface Provider {
    Sender produce();
}

interface Sender {
    void Send();
}

class MailSender implements Sender {

    public void Send() {
        System.out.println("This is mail sender...");
    }
}

class SmsSender implements Sender {

    public void Send() {
        System.out.println("This is sms sender...");
    }
}

class SendMailFactory implements Provider {

    public Sender produce() {
        return new MailSender();
    }
}

class SendSmsFactory implements Provider {

    public Sender produce() {
        return new SmsSender();
    }
}


public class FactoryPattern {
    public static void main(String[] args) {
        Provider provider = new SendMailFactory();
        Sender sender = provider.produce();
        sender.Send();
    }
}

建造者模式

工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。

import java.util.ArrayList;
import java.util.List;

/**
 abstract class Builder {
    /**
     * 第一步:装CPU
     */
   public abstract void buildCPU();

    /**
     * 第二步:装主板
     */
    public abstract void buildMainBoard();

    /**
     * 第三步:装硬盘
     */
    public abstract void buildHD();

    /**
     * 获得组装好的电脑
     * @return
     */
    public abstract Computer getComputer();
}

/**
 * 装机人员装机
 */
class Director {
    public void Construct(Builder builder) {
        builder.buildCPU();
        builder.buildMainBoard();
        builder.buildHD();
    }
}

/**
 * 具体的装机人员
 */
class ConcreteBuilder extends  Builder {

    Computer computer = new Computer();

    @Override
    public void buildCPU() {
        computer.Add("装CPU");
    }

    @Override
    public void buildMainBoard() {
        computer.Add("装主板");
    }

    @Override
    public void buildHD() {
        computer.Add("装硬盘");
    }

    @Override
    public Computer getComputer() {
        return computer;
    }
}

class Computer {

    /**
     * 电脑组件集合
     */
    private List<String> parts = new ArrayList<String>();

    public void Add(String part) {
        parts.add(part);
    }

    public void print() {
        for (int i = 0; i < parts.size(); i++) {
            System.out.println("组件:" + parts.get(i) + "装好了...");
        }
        System.out.println("电脑组装完毕...");
    }
}

public class BuilderPattern {

    public static void main(String[] args) {
        Director director = new Director();
        Builder builder = new ConcreteBuilder();
        director.Construct(builder);
        Computer computer = builder.getComputer();
        computer.print();
    }
}

原型模式(Prototype Pattern)

  Java原型模式(Prototype Pattern)是一种创建型设计模式,其目的是通过复制现有对象来创建新的对象。

使用场景

  • 当对象创建的过程比较耗时或者比较复杂,例如需要进行复杂的计算或者涉及到网络请求等操作,可以使用原型模式来避免重复的初始化过程。
  • 当需要创建的对象需要和其他对象进行协同工作时,例如需要创建一个包含多个对象的组合对象,可以使用原型模式来复制一个已有的组合对象,然后进行修改来创建新的组合对象。
  • 当需要动态地增加或者删除一些对象时,可以使用原型模式来复制一个已有的对象,然后进行修改来创建新的对象。
  • 当需要保护对象的复杂状态时,例如当一个对象的创建需要大量的数据初始化时,可以使用原型模式来保护这些数据,避免因为对象的复制而产生意外的副作用。

代码实现

// 定义一个原型接口
interface Prototype {
    public Prototype clone();
}

// 具体的原型类
class ConcretePrototype implements Prototype {
    public Prototype clone() {
        return new ConcretePrototype();
    }
}

// 客户端代码
class Client {
    public static void main(String[] args) {
        Prototype prototype = new ConcretePrototype();
        Prototype clone = prototype.clone();
    }
}

使用小结

  1. Java中的Object类实现了Cloneable接口,这就意味着Java中的任何对象都可以实现原型模式。通过实现Cloneable接口,并重写Object类中的clone()方法,可以实现原型模式。例如 ArrayList、HashMap 等集合类都实现了Cloneable 接口,可以通过复制现有对象来创建新的对象。
  2. Java中的线程池也是使用了原型模式,线程池中的每个线程都是从原型线程中复制而来,而不是每次创建新的线程。



结构型设计模式

适配器设计模式

适配器模式是将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的的类的兼容性问题。主要分三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

1. 类的适配器模式:

class Source {
    public void method1() {
        System.out.println("This is original method...");
    }
}

interface Targetable {

    /**
     * 与原类中的方法相同
     */
    public void method1();

    /**
     * 新类的方法
     */
    public void method2();
}

class Adapter extends Source implements Targetable {

    @Override
    public void method2() {
        System.out.println("This is the targetable method...");
    }
}

public class AdapterPattern {
    public static void main(String[] args) {
        Targetable targetable = new Adapter();
        targetable.method1();
        targetable.method2();
    }
}

2. 对象的适配器模式

基本思路和类的适配器模式相同,只是将Adapter 类作修改,这次不继承Source 类,而是持有Source 类的实例,以达到解决兼容性的问题。

class Source {
    public void method1() {
        System.out.println("This is original method...");
    }
}

interface Targetable {

    /**
     * 与原类中的方法相同
     */
    public void method1();

    /**
     * 新类的方法
     */
    public void method2();
}

class Wrapper implements Targetable {

    private Source source;

    public Wrapper(Source source) {
        super();
        this.source = source;
    }

    @Override
    public void method1() {
        source.method1();
    }

    @Override
    public void method2() {
        System.out.println("This is the targetable method...");
    }
}

public class AdapterPattern {
    public static void main(String[] args) {
        Source source = new Source();
        Targetable targetable = new Wrapper(source);
        targetable.method1();
        targetable.method2();
    }
}

3. 接口的适配器模式

接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。

/**
 * 定义端口接口,提供通信服务
 */
interface Port {
    /**
     * 远程SSH端口为22
     */
    void SSH();

    /**
     * 网络端口为80
     */
    void NET();

    /**
     * Tomcat容器端口为8080
     */
    void Tomcat();

    /**
     * MySQL数据库端口为3306
     */
    void MySQL();
}

/**
 * 定义抽象类实现端口接口,但是什么事情都不做
 */
abstract class Wrapper implements Port {
    @Override
    public void SSH() {

    }

    @Override
    public void NET() {

    }

    @Override
    public void Tomcat() {

    }

    @Override
    public void MySQL() {

    }
}

/**
 * 提供聊天服务
 * 需要网络功能
 */
class Chat extends Wrapper {
    @Override
    public void NET() {
        System.out.println("Hello World...");
    }
}

/**
 * 网站服务器
 * 需要Tomcat容器,Mysql数据库,网络服务,远程服务
 */
class Server extends Wrapper {
    @Override
    public void SSH() {
        System.out.println("Connect success...");
    }

    @Override
    public void NET() {
        System.out.println("WWW...");
    }

    @Override
    public void Tomcat() {
        System.out.println("Tomcat is running...");
    }

    @Override
    public void MySQL() {
        System.out.println("MySQL is running...");
    }
}

public class AdapterPattern {

    private static Port chatPort = new Chat();
    private static Port serverPort = new Server();

    public static void main(String[] args) {
        // 聊天服务
        chatPort.NET();

        // 服务器
        serverPort.SSH();
        serverPort.NET();
        serverPort.Tomcat();
        serverPort.MySQL();
    }
}

装饰模式

顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。

interface Shape {
    void draw();
}

/**
 * 实现接口的实体类
 */
class Rectangle implements Shape {

    @Override
    public void draw() {
        System.out.println("Shape: Rectangle...");
    }
}

class Circle implements Shape {

    @Override
    public void draw() {
        System.out.println("Shape: Circle...");
    }
}

/**
 * 创建实现了 Shape 接口的抽象装饰类。
 */
abstract class ShapeDecorator implements Shape {
    protected Shape decoratedShape;

    public ShapeDecorator(Shape decoratedShape) {
        this.decoratedShape = decoratedShape;
    }

    @Override
    public void draw() {
        decoratedShape.draw();
    }
}

/**
 *  创建扩展自 ShapeDecorator 类的实体装饰类。
 */
class RedShapeDecorator extends ShapeDecorator {

    public RedShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    @Override
    public void draw() {
        decoratedShape.draw();
        setRedBorder(decoratedShape);
    }

    private void setRedBorder(Shape decoratedShape) {
        System.out.println("Border Color: Red");
    }
}

/**
 * 使用 RedShapeDecorator 来装饰 Shape 对象。
 */
public class DecoratorPattern {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape redCircle = new RedShapeDecorator(new Circle());
        Shape redRectangle = new RedShapeDecorator(new Rectangle());
        System.out.println("Circle with normal border");
        circle.draw();

        System.out.println("\nCircle of red border");
        redCircle.draw();

        System.out.println("\nRectangle of red border");
        redRectangle.draw();
    }
}

代理模式

代理模式指给一个对象提供一个代理对象,并由代理对象控制对原对象的引用。代理可以分为静态代理和动态代理。

通过代理模式,可以利用代理对象为被代理对象添加额外的功能,以此来拓展被代理对象的功能。可以用于计算某个方法执行时间,在某个方法执行前后记录日志等操作。

1. 静态代理

静态代理需要我们写出代理类和被代理类,而且一个代理类和一个被代理类一一对应。代理类和被代理类需要实现同一个接口,通过聚合使得代理对象中有被代理对象的引用,以此实现代理对象控制被代理对象的目的。

/**
 * 代理类和被代理类共同实现的接口
 */
interface IService {

    void service();
}


/**
 * 被代理类
 */
class Service implements IService{

    @Override
    public void service() {
        System.out.println("被代理对象执行相关操作");
    }
}

/**
 * 代理类
 */
class ProxyService implements IService{
    /**
     * 持有被代理对象的引用
     */
    private IService service;

    /**
     * 默认代理Service类
     */
    public ProxyService() {
        this.service = new Service();
    }

    /**
     * 也可以代理实现相同接口的其他类
     * @param service
     */
    public ProxyService(IService service) {
        this.service = service;
    }

    @Override
    public void service() {
        System.out.println("开始执行service()方法");
        service.service();
        System.out.println("service()方法执行完毕");
    }
}


//测试类
public class ProxyPattern {

    public static void main(String[] args) {
        IService service = new Service();
        //传入被代理类的对象
        ProxyService proxyService = new ProxyService(service);
        proxyService.service();
    }
}

2. 动态代理

JDK 1.3 之后,Java通过java.lang.reflect包中的三个类Proxy、InvocationHandler、Method来支持动态代理。动态代理常用于有若干个被代理的对象,且为每个被代理对象添加的功能是相同的(例如在每个方法运行前后记录日志)。

动态代理的代理类不需要我们编写,由Java自动产生代理类源代码并进行编译最后生成代理对象。

创建动态代理对象的步骤:

1. 指明一系列的接口来创建一个代理对象

2. 创建一个调用处理器(InvocationHandler)对象

3. 将这个代理指定为某个其他对象的代理对象

4. 在调用处理器的invoke()方法中采取代理,一方面将调用传递给真实对象,另一方面执行各种需要的操作

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 代理类和被代理类共同实现的接口
 */
interface IService {
    void service();
}

class Service implements IService{

    @Override
    public void service() {
        System.out.println("被代理对象执行相关操作");
    }
}

class ServiceInvocationHandler implements InvocationHandler {

    /**
     * 被代理的对象
     */
    private Object srcObject;

    public ServiceInvocationHandler(Object srcObject) {
        this.srcObject = srcObject;
    }

    @Override
    public Object invoke(Object proxyObj, Method method, Object[] args) throws Throwable {
        System.out.println("开始执行"+method.getName()+"方法");
        //执行原对象的相关操作,容易忘记
        Object returnObj = method.invoke(srcObject,args);
        System.out.println(method.getName()+"方法执行完毕");
        return returnObj;
    }
}

public class ProxyPattern {
    public static void main(String[] args) {
        IService service = new Service();
        Class<? extends IService> clazz = service.getClass();

        IService proxyService = (IService) Proxy.newProxyInstance(clazz.getClassLoader(),
                                        clazz.getInterfaces(), new ServiceInvocationHandler(service));
        proxyService.service();
    }
}

桥接模式(Bridge)

概述

桥接模式是一种结构性设计模式,它将抽象和实现分开,以便它们可以独立地变化。它通过使用组合来代替继承来实现这个目标。

在桥接模式中,有两个类层次结构:抽象类和实现类。抽象类定义了一个接口,它包含了一些基本的操作方法。实现类实现了这些操作方法,并将它们映射到具体的实现上。通过这种方式,客户端代码和具体的实现之间就解耦了。

模式结构

桥接模式的结构包括四个主要部分:

  1. 抽象化(Abstraction):定义抽象类,并包含一个指向实现化对象的引用,抽象类的接口将调用实现化类中的方法。
  2. 实现化(Implementor):定义实现化接口,具体实现化类将实现此接口。
  3. 具体抽象化(Refined Abstraction):扩展抽象化接口以支持更多功能,实现类将实现这些方法。
  4. 具体实现化(Concrete Implementor):实现实现化接口的类,并提供具体实现。

其结构图如图所示:

Java 中设计模式_设计模式

优缺点

使用桥接模式的主要优势在于它能够将抽象和实现分离开来,并将它们独立地变化。这意味着,如果你需要改变实现方式,你只需要改变实现类,而不需要改变客户端代码。

  此外,使用桥接模式可以提高代码的可读性和可维护性。因为它将功能分解成小的模块,而不是大而复杂的类。这使得代码更容易理解、调试和修改。

桥接模式的主要缺点在于它增加了代码的复杂性。因为它需要将抽象和实现分开,所以需要增加更多的类和层次结构。这可能会使代码难以理解、调试和维护。

使用场景

桥接模式适用于以下场景:

  • 当你需要将一个抽象类和它的实现分开时。
  • 当你需要支持多种平台或多种操作系统时。
  • 当你需要改变一个类的实现方式时,而不想影响到客户端代码。

实现

下面通过一个例子来说明桥接模式的实现过程。

  假设我们要设计一个图形库,其中包含两个基本功能:绘制不同种类的图形和使用不同的颜色进行填充。我们可以使用桥接模式来实现这个图形库,其中抽象部分为Shape,实现部分为Color,桥接接口为DrawAPI

先看一下Shape的抽象类:

package com.example.javaDesignPattern.bridge;

/**
 * 抽象类
 *
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 11:19
 */
public abstract class Shape {
    protected DrawAPI drawAPI;

    public Shape(DrawAPI drawAPI) {
        this.drawAPI = drawAPI;
    }

    public abstract void draw();
}

  可以看到,Shape包含了一个对DrawAPI的引用,并且定义了一个抽象的draw方法,用于绘制图形。

再看一下DrawAPI的接口:

package com.example.javaDesignPattern.bridge;

/**
 * 抽象接口
 *
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 11:19
 */
public interface DrawAPI {
    public void draw();
}

  DrawAPI定义了一个draw方法,Shape的实现类将通过它来使用不同的颜色进行填充。

然后是Shape的实现类:CircleRectangle和Triangle

package com.example.javaDesignPattern.bridge;

/**
 * Shape的实现类
 * 
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 11:20
 */
public class Circle extends Shape {
    private int x, y, radius;

    public Circle(int x, int y, int radius, DrawAPI drawAPI) {
        super(drawAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    public void draw() {
        drawAPI.draw();
    }
}
package com.example.javaDesignPattern.bridge;

/**
 * Shape的实现类
 * 
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 11:21
 */
public class Rectangle extends Shape {
    private int x, y, width, height;

    public Rectangle(int x, int y, int width, int height, DrawAPI drawAPI) {
        super(drawAPI);
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    public void draw() {
        drawAPI.draw();
    }
}
package com.example.javaDesignPattern.bridge;

/**
 * Shape的实现类
 *
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 11:22
 */
public class Triangle extends Shape {
    private int x1, y1, x2, y2, x3, y3;

    public Triangle(int x1, int y1, int x2, int y2, int x3, int y3, DrawAPI drawAPI) {
        super(drawAPI);
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
        this.x3 = x3;
        this.y3 = y3;
    }

    public void draw() {
        drawAPI.draw();
    }
}

  可以看到,它们都继承了Shape,并实现了draw方法。在构造方法中,将DrawAPI对象传递给了Shape的构造方法,从而实现了抽象部分和实现部分的连接。

最后是Color的实现类:RedGreenBlue

package com.example.javaDesignPattern.bridge;

/**
 * Color的实现类
 *
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 11:24
 */
public class Red implements DrawAPI {
    public void draw() {
        System.out.println("fill with red");
    }
}
public class Green implements DrawAPI {
    public void draw() {
        System.out.println("fill with green");
    }
}
public class Blue implements DrawAPI {
    public void draw() {
        System.out.println("fill with blue");
    }
}

  它们都实现了DrawAPI接口,并提供了具体的draw方法,用于绘制不同的颜色。

最后我们可以编写一个测试用例来验证桥接模式的实现:

package com.example.javaDesignPattern.bridge;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 11:15
 */
public class Client {

    public static void main(String[] args) {
        Shape circle = new Circle(100,100, 10, new Red());
        circle.draw();

        Shape rectangle = new Rectangle(50, 50, 20, 30, new Green());
        rectangle.draw();

        Shape triangle = new Triangle(10, 20, 30, 40, 50, 60, new Blue());
        triangle.draw();
    }
}

执行结果如下:

Java 中设计模式_Java_02

代码分析:   如上代码是一个简单的示例程序,其中定义了一个Client类。在main方法中,创建了三个形状对象circlerectangletriangle,并使用draw方法绘制了它们。

这三个形状对象分别是:

  • Circle:圆形,构造函数中参数依次为圆心位置x、y坐标、半径和颜色对象。
  • Rectangle:矩形,构造函数中参数依次为左上角位置x、y坐标、宽度、高度和颜色对象。
  • Triangle:三角形,构造函数中参数依次为三个顶点的x、y坐标和颜色对象。

  在这里,颜色对象是使用了简单工厂模式创建的,包括RedGreenBlue

组合模式

简介

组合模式 : 将 对象 组合成 树形结构 , 表示 " 部分-整体 " 层次结构 ;

组合模式 使 客户端 对 单个对象 和 组合对象 保持一致的 方式处理 ;

如 : 文件系统 , 根目录下有若干文件和目录 , 在二级目录下还有目录和文件 , 这种情况下 , 适合使用组合模式 ;

适用场景

  • 忽略差异 : 希望 客户端 可以 忽略 组合对象 与 单个对象 的差异 ;
  • 处理树形结构 ;

优缺点

组合模式优点 :


定义层次 : 清楚地 定义 分层次 的 复杂对象 , 表示 对象 的 全部 或 部分 层次 ;

忽略层次 : 让 客户端 忽略 层次之间的差异 , 方便对 整个层次结构 进行控制 ;

简化客户端代码 ;

符合开闭原则 ;


组合模式缺点 :


限制类型复杂 : 限制类型时 , 比较复杂 ;

如 : 某个目录中只能包含文本文件 , 使用组合模式时 , 不能依赖类型系统 , 施加约束 , 它们都来自于节点的抽象层 ; 在这种情况下 , 必须通过在运行时进行类型检查 , 这样就变得比较复杂 ;

使设计变得更加抽象 ;

组合模式和访问者模式 : 两个模式经常结合起来使用 , 使用 访问者模式 , 访问 组合模式 中的 递归结构 ,

示例

业务逻辑 :

书籍 分属于 目录 下 , 如果使 书籍目录 和 书籍 继承同一个抽象类 ( 目录组件 ) , 可以将 书籍 及 由书籍组成的书籍目录 当做同一个类型的对象进行操作 , 操作上的具体的差别 , 进行定制化处理 ;

1、书籍和目录的抽象父类


抽象类中所有的方法 都抛出异常 , 子类重写需要的方法 , 如果子类实例对象调用没有重写的方法 , 就会抛出异常 ;

package composite;

/**
 * 目录组件
 *      目录 和 书籍 都继承 CatalogComponent 接口
 *      子类根据当前的的类型 , 选择性重写接口
 */
public abstract class CatalogComponent {
    /**
     * 添加元素
     * @param catalogComponent
     */
    public void add(CatalogComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持的操作");
    }

    /**
     * 删除元素
     * @param catalogComponent
     */
    public void remove(CatalogComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持的操作");
    }

    /**
     * 获取名称
     * @param catalogComponent
     */
    public String getName(CatalogComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持的操作");
    }

    /**
     * 获取价格
     * @param catalogComponent
     * @return
     */
    public double getPrice(CatalogComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持的操作");
    }

    /**
     * 如果该组件是目录 , 直接打印目录
     * 如果该组件是书籍 , 直接打印课程
     */
    public void printf() {
        throw new UnsupportedOperationException("不支持的操作");
    }


}


2、书籍类

书籍类 重写了 CatalogComponent 的 getName / getPrice / printf 方法 , 如果调用其余方法 , 会抛出异常 ;

package composite;

/**
 * 书籍类
 *      重写了 CatalogComponent 的 getName / getPrice / printf 方法
 *      如果调用其余方法 , 会抛出异常
 */
public class Book extends CatalogComponent {
    /**
     * 书籍名称
     */
    private String name;

    /**
     * 书籍价格
     */
    private double price;

    public Book(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String getName(CatalogComponent catalogComponent) {
        return this.name;
    }

    @Override
    public double getPrice(CatalogComponent catalogComponent) {
        return this.price;
    }

    @Override
    public void printf() {
        System.out.println("Book : name = " + name + " , price = " + price);
    }
}


3、目录类


目录类 重写了 CatalogComponent 的 getName / add / remove / printf 方法 , 如果调用其余方法 , 会抛出异常 ;


package composite;

import java.util.ArrayList;

/**
 * 目录类
 *      重写了 CatalogComponent 的 getName / add / remove / printf 方法
 *      如果调用其余方法 , 会抛出异常
 */
public class Catalogue extends CatalogComponent {
    /**
     * 维护书籍列表
     */
    private ArrayList<CatalogComponent> items = new ArrayList<>();

    /**
     * 书籍名称
     */
    private String name;

    /**
     * 存储当前目录的层数
     *      最外面的是 0 层
     */
    private Integer level;

    public Catalogue(String name, Integer level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public String getName(CatalogComponent catalogComponent) {
        return this.name;
    }

    @Override
    public void add(CatalogComponent catalogComponent) {
        items.add(catalogComponent);
    }

    @Override
    public void remove(CatalogComponent catalogComponent) {
        items.remove(catalogComponent);
    }

    @Override
    public void printf() {
        System.out.println(this.name + " : ");
        for (CatalogComponent catalogComponent : items) {
            // 这样可以将目录层次完整的打印出来
            if (this.level != null) {
                for (int i = 0; i < level; i++) {
                    System.out.print("  ");
                }
            }
            catalogComponent.printf();
        }
    }
}


4、测试类


package composite;

public class Main {
    public static void main(String[] args) {
        // 认为 书籍 和 目录 都是 CatalogComponent 类型的
        CatalogComponent storyBook = new Book("故事书", 2.0);
        CatalogComponent novelBook = new Book("小说", 8.0);

        CatalogComponent mathBook = new Book("数学书", 7.0);
        CatalogComponent englishBook = new Book("英语书", 3.0);

        CatalogComponent schoolBooks = new Catalogue("学校课本", 2);
        schoolBooks.add(mathBook);
        schoolBooks.add(englishBook);

        // 主目录 , 包含上述所有内容 , 2 本书 和 1 个目录
        CatalogComponent main = new Catalogue("所有书籍", 1);
        main.add(storyBook);
        main.add(novelBook);
        main.add(schoolBooks);

        // 打印主目录
        main.printf();
    }
}




执行结果 :


所有书籍 : 
  Book : name = 故事书 , price = 2.0
  Book : name = 小说 , price = 8.0
  学校课本 : 
    Book : name = 数学书 , price = 7.0
    Book : name = 英语书 , price = 3.0


享元模式

简介

在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。

例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。

定义与特点

享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

其主要缺点是:

  1. 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
  2. 读取享元模式的外部状态会使得运行时间稍微变长。
结构与实现

享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。

  • 内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变;
  • 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。

比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

享元模式的本质是缓存共享对象,降低内存消耗。

1. 模式的结构

享元模式的主要角色有如下。

  1. 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

图 1 是享元模式的结构图,其中:

  • UnsharedConcreteFlyweight 是非享元角色,里面包含了非共享的外部状态信息 info;
  • Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
  • ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
  • FlyweightFactory 是享元工厂角色,它是关键字 key 来管理具体享元;
  • 客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。



Java 中设计模式_Java_03

图1 享元模式的结构图

2. 模式的实现

享元模式的实现代码如下:

public class FlyweightPattern {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight f01 = factory.getFlyweight("a");
        Flyweight f02 = factory.getFlyweight("a");
        Flyweight f03 = factory.getFlyweight("a");
        Flyweight f11 = factory.getFlyweight("b");
        Flyweight f12 = factory.getFlyweight("b");
        f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));
        f02.operation(new UnsharedConcreteFlyweight("第2次调用a。"));
        f03.operation(new UnsharedConcreteFlyweight("第3次调用a。"));
        f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));
        f12.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
    }
}
//非享元角色
class UnsharedConcreteFlyweight {
    private String info;
    UnsharedConcreteFlyweight(String info) {
        this.info = info;
    }
    public String getInfo() {
        return info;
    }
    public void setInfo(String info) {
        this.info = info;
    }
}
//抽象享元角色
interface Flyweight {
    public void operation(UnsharedConcreteFlyweight state);
}
//具体享元角色
class ConcreteFlyweight implements Flyweight {
    private String key;
    ConcreteFlyweight(String key) {
        this.key = key;
        System.out.println("具体享元" + key + "被创建!");
    }
    public void operation(UnsharedConcreteFlyweight outState) {
        System.out.print("具体享元" + key + "被调用,");
        System.out.println("非享元信息是:" + outState.getInfo());
    }
}
//享元工厂角色
class FlyweightFactory {
    private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
    public Flyweight getFlyweight(String key) {
        Flyweight flyweight = (Flyweight) flyweights.get(key);
        if (flyweight != null) {
            System.out.println("具体享元" + key + "已经存在,被成功获取!");
        } else {
            flyweight = new ConcreteFlyweight(key);
            flyweights.put(key, flyweight);
        }
        return flyweight;
    }
}

程序运行结果如下:

具体享元a被创建!
具体享元a已经存在,被成功获取!
具体享元a已经存在,被成功获取!
具体享元b被创建!
具体享元b已经存在,被成功获取!
具体享元a被调用,非享元信息是:第1次调用a。
具体享元a被调用,非享元信息是:第2次调用a。
具体享元a被调用,非享元信息是:第3次调用a。
具体享元b被调用,非享元信息是:第1次调用b。
具体享元b被调用,非享元信息是:第2次调用b。
应用实例

【例1】享元模式在五子棋游戏中的应用。

分析:五子棋同围棋一样,包含多个“黑”或“白”颜色的棋子,所以用享元模式比较好。

本实例中:

  • 棋子(ChessPieces)类是抽象享元角色,它包含了一个落子的 DownPieces(Graphics g,Point pt) 方法;
  • 白子(WhitePieces)和黑子(BlackPieces)类是具体享元角色,它实现了落子方法;
  • Point 是非享元角色,它指定了落子的位置;
  • WeiqiFactory 是享元工厂角色,它通过 ArrayList 来管理棋子,并且提供了获取白子或者黑子的 getChessPieces(String type) 方法;
  • 客户类(Chessboard)利用 Graphics 组件在框架窗体中绘制一个棋盘,并实现 mouseClicked(MouseEvent e) 事件处理方法,该方法根据用户的选择从享元工厂中获取白子或者黑子并落在棋盘上。

图 2 所示是其结构图。



Java 中设计模式_设计模式_04

图2 五子棋游戏的结构图

程序代码如下:

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
public class WzqGame {
    public static void main(String[] args) {
        new Chessboard();
    }
}
//棋盘
class Chessboard extends MouseAdapter {
    WeiqiFactory wf;
    JFrame f;
    Graphics g;
    JRadioButton wz;
    JRadioButton bz;
    private final int x = 50;
    private final int y = 50;
    private final int w = 40;    //小方格宽度和高度
    private final int rw = 400;    //棋盘宽度和高度
    Chessboard() {
        wf = new WeiqiFactory();
        f = new JFrame("享元模式在五子棋游戏中的应用");
        f.setBounds(100, 100, 500, 550);
        f.setVisible(true);
        f.setResizable(false);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel SouthJP = new JPanel();
        f.add("South", SouthJP);
        wz = new JRadioButton("白子");
        bz = new JRadioButton("黑子", true);
        ButtonGroup group = new ButtonGroup();
        group.add(wz);
        group.add(bz);
        SouthJP.add(wz);
        SouthJP.add(bz);
        JPanel CenterJP = new JPanel();
        CenterJP.setLayout(null);
        CenterJP.setSize(500, 500);
        CenterJP.addMouseListener(this);
        f.add("Center", CenterJP);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        g = CenterJP.getGraphics();
        g.setColor(Color.BLUE);
        g.drawRect(x, y, rw, rw);
        for (int i = 1; i < 10; i++) {
            //绘制第i条竖直线
            g.drawLine(x + (i * w), y, x + (i * w), y + rw);
            //绘制第i条水平线
            g.drawLine(x, y + (i * w), x + rw, y + (i * w));
        }
    }
    public void mouseClicked(MouseEvent e) {
        Point pt = new Point(e.getX() - 15, e.getY() - 15);
        if (wz.isSelected()) {
            ChessPieces c1 = wf.getChessPieces("w");
            c1.DownPieces(g, pt);
        } else if (bz.isSelected()) {
            ChessPieces c2 = wf.getChessPieces("b");
            c2.DownPieces(g, pt);
        }
    }
}
//抽象享元角色:棋子
interface ChessPieces {
    public void DownPieces(Graphics g, Point pt);    //下子
}
//具体享元角色:白子
class WhitePieces implements ChessPieces {
    public void DownPieces(Graphics g, Point pt) {
        g.setColor(Color.WHITE);
        g.fillOval(pt.x, pt.y, 30, 30);
    }
}
//具体享元角色:黑子
class BlackPieces implements ChessPieces {
    public void DownPieces(Graphics g, Point pt) {
        g.setColor(Color.BLACK);
        g.fillOval(pt.x, pt.y, 30, 30);
    }
}
//享元工厂角色
class WeiqiFactory {
    private ArrayList<ChessPieces> qz;
    public WeiqiFactory() {
        qz = new ArrayList<ChessPieces>();
        ChessPieces w = new WhitePieces();
        qz.add(w);
        ChessPieces b = new BlackPieces();
        qz.add(b);
    }
    public ChessPieces getChessPieces(String type) {
        if (type.equalsIgnoreCase("w")) {
            return (ChessPieces) qz.get(0);
        } else if (type.equalsIgnoreCase("b")) {
            return (ChessPieces) qz.get(1);
        } else {
            return null;
        }
    }
}

程序运行结果如图 3 所示。


Java 中设计模式_Java_05

图3 五子棋游戏的运行结果

享元模式的应用场景

当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。

享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。

前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
享元模式的扩展

在前面介绍的享元模式中,其结构图通常包含可以共享的部分和不可以共享的部分。在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:单纯享元模式和复合享元模式,下面分别对它们进行简单介绍。

(1) 单纯享元模式,这种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类,其结构图如图 4 所示。



Java 中设计模式_Java_06

图4 单纯享元模式的结构图

(2) 复合享元模式,这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享,其结构图如图 5 所示。



Java 中设计模式_Java_07

图5 复合享元模式的结构图

行为型设计模式

策略模式

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数。策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。

/**
 * 抽象算法的策略类,定义所有支持的算法的公共接口
 */
abstract class Strategy {
    /**
     * 算法方法
     */
    public abstract void AlgorithmInterface();
}

/**
 * 具体算法A
 */
class ConcreteStrategyA extends Strategy {
    //算法A实现方法
    @Override
    public void AlgorithmInterface() {
        System.out.println("算法A的实现");
    }
}

/**
 * 具体算法B
 */
class ConcreteStrategyB extends Strategy {
    /**
     * 算法B实现方法
     */
    @Override
    public void AlgorithmInterface() {
        System.out.println("算法B的实现");
    }
}

/**
 * 具体算法C
 */
class ConcreteStrategyC extends Strategy {
    @Override
    public void AlgorithmInterface() {
        System.out.println("算法C的实现");
    }
}

/**
 * 上下文,维护一个对策略类对象的引用
 */
class Context {
    Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void contextInterface(){
        strategy.AlgorithmInterface();
    }
}

/**
 * 客户端代码:实现不同的策略
 */
public class StrategyPattern {
    public static void main(String[] args) {

        Context context;

        context = new Context(new ConcreteStrategyA());
        context.contextInterface();

        context = new Context(new ConcreteStrategyB());
        context.contextInterface();

        context = new Context(new ConcreteStrategyC());
        context.contextInterface();
    }
}

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月31日 0

暂无评论

推荐阅读
h9htfs4cnhmS