二十四种设计模式与六大设计原则(四):【状态模式、原型模式、中介者模式、解释器模式、享元模式、备忘录模式】的定义、举例说明、核心思想、适用场景和优缺点

接上次博客:二十四种设计模式与六大设计原则(三):【装饰模式、迭代器模式、组合模式、观察者模式、责任链模式、访问者模式】的定义、举例说明、核心思想、适用场景和优缺点-CSDN博客

目录

状态模式【State Pattern】

定义

举例说明

核心思想

适用场景

优缺点

原型模式【Prototype Pattern】

定义

举例说明

核心思想

适用场景

优缺点

中介者模式【Mediator Pattern】

定义

举例说明

核心思想

适用场景

优缺点

解释器模式【Interpreter Pattern】

定义

举例说明

核心思想

适用场景

优缺点

亨元模式【Flyweight Pattern】

定义

举例说明

核心思想

适用场景

优缺点

备忘录模式【Memento Pattern】

定义

举例说明

核心思想

适用场景

优缺点


 

状态模式【State Pattern】

定义

状态模式是一种行为设计模式,它允许对象在内部状态发生改变时改变它的行为。这种模式将对象的状态封装成独立的类,并将行为委托给表示当前状态的对象。通过这种方式,状态模式使得对象在不同的状态下可以有不同的行为,并且能够在运行时动态地改变对象的状态。

具体来说,状态模式包括三个主要角色:

  1. 上下文(Context):上下文是拥有状态的对象,它维护一个当前状态对象的引用,并在状态发生改变时调用状态对象的方法来执行相应的行为。

  2. 状态(State):状态是表示对象当前状态的接口或抽象类,它定义了对象在该状态下可以执行的行为。

  3. 具体状态(Concrete State):具体状态是状态的具体实现,它实现了状态接口或继承了状态抽象类,并定义了对象在该状态下具体的行为。

通过状态模式,对象的行为可以根据内部状态的改变而改变,同时将每种状态的行为都封装在了对应的状态类中,使得状态转换和状态行为的管理变得简单且灵活。

状态模式:State

 

[GoF, p305] 是指《设计模式:可复用面向对象软件的基础》一书中的页码和章节号。这本书是由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四位作者合著,通常被简称为《设计模式》或《Gang of Four (GoF)》书籍。在这本书中,第 305 页是关于状态模式的章节,它详细介绍了状态模式的定义、结构、参与角色以及应用场景等内容。

当我们谈论对象的状态时,我们指的是对象在特定时间点所处的情况或条件。对象的状态可能由其属性的值或其他因素决定。在编程中,我们经常需要根据对象的状态来决定其行为。状态模式允许我们更好地组织和管理对象的行为,以响应其状态的变化。

让我们来解释一下这句话的含义:

"Allow an object to alter its behavior when its internal state changes."

这句话的意思是说,当对象的内部状态发生变化时,状态模式允许对象改变其行为。换句话说,对象不再局限于固定的行为,而是根据其状态动态地改变行为。这样,对象可以在不同的情况下表现出不同的行为,从而使其更具灵活性和适应性。

举个简单的例子,假设有一个游戏角色对象,它可以处于不同的状态,比如"正常状态"、"受伤状态"和"死亡状态"。在正常状态下,角色可以自由移动和攻击;在受伤状态下,角色移动速度减慢并且不能进行攻击;在死亡状态下,角色不能移动或攻击,游戏可能会触发一些死亡动画或者显示游戏结束画面。

通过状态模式,我们可以将不同状态对应的行为封装到不同的状态类中,然后根据角色当前的状态来调用相应的行为。这样,当角色的状态发生变化时,它的行为也会相应地改变,而外部使用角色对象的代码则无需关心状态的变化,只需调用对象的方法即可。

"The object will appear to change its class."

这句话的意思是说,当对象的行为随着状态的改变而改变时,外部看起来就好像对象所属的类发生了改变一样。换句话说,对象的行为和特性在不同状态下会表现出不同的样子,就像是它们属于不同的类一样。这种看起来的类变化是因为状态模式将对象的状态和行为进行了有效地封装和组织,使得对象的行为更加灵活多变。

举例说明

假设你是一家游乐园的管理者,你想要设计一个游乐园门票系统,根据游客的年龄和身高来确定他们的门票类型和价格。门票类型包括成人票、儿童票和婴儿票,价格也随之而定。游客在游乐园内的状态也会随着游玩的过程而改变,比如进入游乐设施、排队等待、乘坐游乐设施、离开游乐设施等状态。

在这个场景中,你可以使用状态模式来管理游客的状态和门票类型。每个游客都可以视为一个具有不同状态的对象,比如年龄、身高、是否在排队等。根据游客的状态,你可以决定他们应该购买什么类型的门票以及对应的价格。同时,游客在游乐园内的状态变化也会影响他们的行为和体验,比如排队等待时不能乘坐游乐设施,乘坐游乐设施后状态会变为游玩中,等等。

这个例子中,游乐园管理系统充当了状态模式中的上下文(Context),而游客的不同状态则对应状态模式中的具体状态(ConcreteState)。通过状态模式,游乐园管理者可以根据游客的不同状态来决定他们的行为和门票类型,从而更好地管理游乐园的运营。

首先,我们需要定义游客的状态。游客的状态可以包括以下几种:

  1. 在游乐园外排队购票(QueueingState)
  2. 在游乐园内等待入场(WaitingState)
  3. 游玩中(PlayingState)
  4. 离开游乐园(LeavingState)

接下来,我们定义游客的年龄和身高,以确定他们应该购买的门票类型和价格。门票类型可以包括:

  1. 成人票(AdultTicket)
  2. 儿童票(ChildTicket)
  3. 婴儿票(InfantTicket)

价格也应根据门票类型而定。

然后,我们需要实现状态接口(State),并为每个具体状态(ConcreteState)实现相应的状态类,如排队购票状态、等待入场状态、游玩中状态和离开游乐园状态。这些状态类应该能够处理游客在不同状态下的行为,比如排队、等待、游玩、离开等。

接着,我们设计游乐园门票系统的上下文(Context),即游乐园管理系统。游乐园管理系统应该能够根据游客的状态和属性来确定他们应该购买的门票类型和价格,并且在游玩过程中及时更新游客的状态。

最后,我们将游客的状态切换和门票购买过程与游乐园管理系统相结合,实现状态的转换和门票的购买。

通过状态模式,我们可以使游乐园门票系统更加灵活和可扩展,能够根据不同的情况自动调整门票类型和价格,提供更好的服务和体验。

// 状态接口
interface State {
    void handle(Visitor visitor);
}

// 游客类
class Visitor {
    private String name;
    private int age;
    private double height;
    private State state;

    public Visitor(String name, int age, double height) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.state = new QueueingState();
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public double getHeight() {
        return height;
    }

    public void setState(State state) {
        this.state = state;
    }

    public void performAction() {
        state.handle(this);
    }
}

// 游乐园管理系统
class ParkManagementSystem {
    public void manage(Visitor visitor) {
        visitor.performAction();
    }
}

// 具体状态:排队购票状态
class QueueingState implements State {
    @Override
    public void handle(Visitor visitor) {
        System.out.println(visitor.getName() + "正在排队购票...");
        // 根据年龄确定门票类型和价格
        Ticket ticket = TicketFactory.createTicket(visitor.getAge());
        System.out.println(visitor.getName() + "购票成功,门票类型为:" + ticket.getType() + ",价格为:" + ticket.getPrice() + "元");
        visitor.setState(new WaitingState());
    }
}

// 具体状态:等待入场状态
class WaitingState implements State {
    @Override
    public void handle(Visitor visitor) {
        System.out.println(visitor.getName() + "已购票,请入场...");
        // 根据游客属性处理状态
        if (visitor.getAge() >= 18) {
            visitor.setState(new PlayingState());
        } else if (visitor.getAge() >= 3) {
            System.out.println("儿童需要成年人陪同,无法独自入场游玩!");
            visitor.setState(new LeavingState());
        } else {
            System.out.println("婴儿免费入场!");
            visitor.setState(new PlayingState());
        }
    }
}

// 具体状态:游玩中状态
class PlayingState implements State {
    @Override
    public void handle(Visitor visitor) {
        System.out.println(visitor.getName() + "正在游玩中...");
        // 游玩过程中的其他操作
        visitor.setState(new LeavingState()); // 游玩结束后直接进入离开游乐园状态
    }
}

// 具体状态:离开游乐园状态
class LeavingState implements State {
    @Override
    public void handle(Visitor visitor) {
        System.out.println(visitor.getName() + "游玩结束,准备离开游乐园...");
        // 离开游乐园过程中的其他操作
    }
}

// 门票接口
interface Ticket {
    String getType();
    double getPrice();
}

// 成人票
class AdultTicket implements Ticket {
    @Override
    public String getType() {
        return "成人票";
    }

    @Override
    public double getPrice() {
        return 50.0; // 成人票价格为50元
    }
}

// 儿童票
class ChildTicket implements Ticket {
    @Override
    public String getType() {
        return "儿童票";
    }

    @Override
    public double getPrice() {
        return 30.0; // 儿童票价格为30元
    }
}

// 婴儿票
class InfantTicket implements Ticket {
    @Override
    public String getType() {
        return "婴儿票";
    }

    @Override
    public double getPrice() {
        return 0.0; // 婴儿票免费
    }
}

// 门票工厂类
class TicketFactory {
    public static Ticket createTicket(int age) {
        if (age >= 18) {
            return new AdultTicket();
        } else if (age >= 3) {
            return new ChildTicket();
        } else {
            return new InfantTicket();
        }
    }
}

// 测试类
public class ParkSimulation {
    public static void main(String[] args) {
        ParkManagementSystem park = new ParkManagementSystem();

        Visitor adultVisitor = new Visitor("小明", 25, 170.0);
        Visitor childVisitor = new Visitor("小红", 8, 120.0);
        Visitor infantVisitor = new Visitor("小强", 2, 80.0);

        System.out.println("===== 初始状态 =====");
        park.manage(adultVisitor);
        park.manage(childVisitor);
        park.manage(infantVisitor);

        System.out.println("\n===== 购票并进入游乐园 =====");
        park.manage(adultVisitor);
        park.manage(childVisitor);
        park.manage(infantVisitor);

        System.out.println("\n===== 游玩中 =====");
        park.manage(adultVisitor);
        park.manage(childVisitor);
        park.manage(infantVisitor);

        System.out.println("\n===== 离开游乐园 =====");
        park.manage(adultVisitor);
        park.manage(childVisitor);
        park.manage(infantVisitor);
    }
}

核心思想

状态模式的核心思想是将对象的行为与其状态分离,并将状态封装成独立的类。对象在不同的状态下表现出不同的行为,而状态之间的转换由对象的状态类来管理。通过这种方式,状态模式使得对象的状态变化对其行为产生影响,同时使得状态的变化更加灵活和可扩展。

具体来说,状态模式的核心思想包括以下几点:

  1. 将对象的状态抽象为独立的类:状态模式将对象的状态抽象为独立的类,每个状态类表示对象在特定状态下的行为。

  2. 封装状态的行为:每个状态类封装了对象在该状态下的行为,对象在不同的状态下具有不同的行为,通过状态类来实现这种行为的封装。

  3. 将状态转换交由状态类来管理:对象在不同状态之间的转换由状态类来管理,对象在状态发生变化时可以切换到新的状态,从而改变其行为。

  4. 上下文对象委托行为给当前状态对象:上下文对象在执行行为时会委托给当前状态对象来处理,不同的状态对象可以对同一个请求做出不同的响应。

总的来说,状态模式的核心思想是通过将对象的状态和行为分离,使得对象的行为可以根据其状态的变化而变化,同时使得状态的管理更加灵活和可扩展。

适用场景

状态模式通常适用于以下情况:

  1. 当一个对象的行为取决于其状态,并且在运行时可能会根据状态改变其行为时,可以考虑使用状态模式。这种情况下,状态模式可以将对象的各种行为细分到不同的状态类中,使得对象在不同状态下可以有不同的行为表现。

  2. 当一个对象的状态转换较为复杂,且包含大量的条件语句时,可以考虑使用状态模式。状态模式可以将状态转换的逻辑封装在状态类中,使得状态之间的转换变得清晰可控,避免了大量的条件判断语句。

  3. 当一个对象的状态转换与其行为之间存在着复杂的关联关系时,可以考虑使用状态模式。状态模式可以将状态转换的逻辑和行为的执行逻辑解耦,使得系统更加灵活和易于扩展。

  4. 当需要在运行时动态地添加新的状态时,可以考虑使用状态模式。状态模式将状态封装成独立的类,使得新增状态的引入变得简单,无需修改现有代码。

总之,状态模式适用于对象的状态较多且复杂、状态之间存在关联关系、需要动态地添加新状态或者对象的行为需要根据状态变化而变化的情况。

关于状态的数量,确实在使用状态模式时需要谨慎考虑。如果状态过多,会导致状态类的增加和代码的复杂性增加,可能会使得维护和理解代码变得困难。通常情况下,最好将状态控制在较少的数量,通常不超过五个,以确保状态模式的简洁性和可维护性。如果状态过多,可以考虑是否可以通过其他设计模式来简化或优化系统的设计。

优缺点

状态模式的优点包括:

  1. 封装性好:状态模式将每个状态封装成独立的类,使得状态之间的转换逻辑和状态的具体实现相互独立,符合单一职责原则和开闭原则,提高了代码的可维护性和可扩展性。

  2. 避免大量的条件判断语句:状态模式通过将状态转换的逻辑封装在状态类中,使得在客户端代码中不再需要大量的条件判断语句,提高了代码的可读性和可维护性。

  3. 状态切换灵活:状态模式将状态转换的逻辑集中在状态类中,可以灵活地添加新的状态或者修改现有的状态转换逻辑,而不会影响到其他状态和客户端代码,使得系统更加灵活和易于扩展。

  4. 符合开闭原则和单一职责原则通过引入状态类,状态模式可以很容易地新增新的状态,而不需要修改已有的代码,符合开闭原则,对系统的扩展性和维护性有利。

状态模式的缺点包括:

  1. 类的数量增加,即类膨胀引入状态类会增加系统中类的数量,可能会导致类的数量过多,增加系统的复杂度和理解难度。

  2. 状态之间的关联关系:状态之间可能存在复杂的关联关系,导致状态转换逻辑较为复杂,需要仔细设计和管理,否则可能会导致系统的混乱和不稳定性。

  3. 不符合迪米特法则:状态模式要求将状态转换的逻辑封装在状态类中,可能会导致状态类之间的相互依赖,不太符合迪米特法则,可能会增加类之间的耦合度。

补充说明:

当我们讨论状态模式时,我们通常首先想到的是简单的状态转换,其中一个状态只能过渡到另一个状态,这种情况下状态模式的应用非常直观。举例来说,TCP协议中的状态转换就是一个经典的例子,TCP连接可以处于等待、连接和断开三种状态之一,并且这些状态之间的转换是有序的、单向的。

然而,在实际的软件开发中,我们常常面对更加复杂和灵活的状态转换需求。有时,一个状态可能可以转换到多个不同的状态,而且状态之间的转换可能具有多种选择,形成了一个复杂的状态转换网络。比如,用户在一个系统中可能具有多种状态,如普通用户、VIP用户、管理员等,而这些状态之间的转换可能是自由的,可能是由用户行为、系统策略或其他外部条件触发的。

在这种情况下,简单的状态模式可能无法很好地满足需求。单纯的状态模式在设计上可能会变得复杂且难以维护,因为它需要处理大量的状态转换逻辑,而且这些逻辑可能会相互交织,导致代码难以理解和扩展。

为了应对这种复杂的状态转换需求,我们可以考虑将状态模式与建造者模式相结合。建造者模式的主要目的是将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。在这种情况下,状态可以被看作是建造者模式中的产品,而状态之间的转换则可以被看作是建造过程。

 具体地,我们可以将每个状态视为一个具体的建造者,负责构建该状态下的行为。而状态之间的转换则可以由一个指挥者来管理,指挥者根据外部条件或触发事件来决定状态之间的转换规则。通过这种方式,我们可以将系统的状态转换逻辑与具体的状态行为进行解耦,使得系统更加灵活和易于扩展。

原型模式【Prototype Pattern】

定义

原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过实例化类。简而言之,原型模式基于现有对象创建新对象,而不是从头开始重新构建。

具体来说,原型模式允许通过复制现有对象来创建新对象,而不是通过实例化类。它包含一个原型抽象类,该类定义了用于复制对象的通用接口方法。具体原型类实现了这个接口,以便根据其自身类型创建对象的副本。

在原型模式中,客户端不需要了解对象的具体类型,只需要知道如何复制原型对象即可。这种方式使得客户端可以动态地创建新对象,而不需要直接依赖于特定的类。原型模式适用于以下情况:

  1. 当创建对象的过程比较复杂或耗时时,可以通过复制现有对象来提高性能。
  2. 当需要避免构造函数的复杂性,或者需要避免子类化创建对象时,可以使用原型模式。
  3. 当需要动态地创建新对象,而不需要知道其具体类型时,原型模式也是一个很好的选择。

原型模式的关键是实现对象的克隆方法,以便创建新对象的副本。在Java中,可以通过实现Cloneable接口并重写clone()方法来实现对象的浅拷贝或深拷贝。浅拷贝复制对象及其所有原始类型属性,而对于引用类型属性,仅复制引用而不复制对象本身;深拷贝则会递归复制对象及其所有属性,包括引用类型属性所引用的对象。在实现原型模式时,我们需要根据具体需求选择适当的克隆方式。

举例说明

假设你是一位手机壳设计师,你的目标是设计出一系列吸引人的手机壳,以满足不同用户的口味和需求。你意识到每次设计新的手机壳都需要花费大量的时间和精力,因此你决定采用原型模式来提高设计效率。

首先,你设计了一款原型手机壳,这款手机壳具有基本的外形、纹理和颜色,但没有特别的装饰或图案。这个原型可以被看作是你的设计基础,你将使用它来创建更多样化的手机壳。

接着,你开始对原型手机壳进行修改和定制,以创造出新的样式。例如,你可能会改变手机壳的颜色,尝试不同的纹理或材质,或者添加装饰性图案或标志。每次修改都是在原型的基础上进行的,你可以灵活地调整细节,以满足不同用户的品味和喜好。

一旦你满意了一个新的设计,你就可以使用原型模式来复制和生产这款手机壳。通过原型模式,你可以快速创建出多个与原型类似但又有所不同的手机壳样式,而不需要从零开始设计每一个。

随着时间的推移,你积累了大量不同样式的手机壳,涵盖了各种颜色、图案和装饰。这使得你的产品线更加丰富多样,可以满足不同用户的需求和偏好。同时,你也节省了大量的设计时间和成本,提高了生产效率和竞争力。

import java.util.Objects;

class PhoneCase implements Cloneable {
    private String shape;
    private String texture;
    private String color;
    private String pattern;

    public PhoneCase() {
        this.shape = "简约";
        this.texture = "光滑";
        this.color = "透明";
        this.pattern = "无";
    }

    public PhoneCase(String shape, String texture, String color, String pattern) {
        this.shape = shape;
        this.texture = texture;
        this.color = color;
        this.pattern = pattern;
    }

    @Override
    public PhoneCase clone() throws CloneNotSupportedException {
        return (PhoneCase) super.clone();
    }

    @Override
    public String toString() {
        return "外形:" + shape + ", 纹理:" + texture + ", 颜色:" + color + ", 图案:" + pattern;
    }

    // getters and setters
    public String getShape() {
        return shape;
    }

    public void setShape(String shape) {
        this.shape = shape;
    }

    public String getTexture() {
        return texture;
    }

    public void setTexture(String texture) {
        this.texture = texture;
    }

    public String getColor() {
        return color;
    }

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

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
}

public class PrototypePatternExample {
    public static void main(String[] args) {
        // 创建原型手机壳
        PhoneCase prototypeCase = new PhoneCase();

        // 显示原型手机壳属性
        System.out.println("原型手机壳属性:");
        System.out.println(prototypeCase);

        try {
            // 克隆手机壳并修改属性
            PhoneCase customizedCase = prototypeCase.clone();
            customizedCase.setColor("红色");
            customizedCase.setPattern("星星");

            // 显示克隆手机壳属性
            System.out.println("\n克隆手机壳属性:");
            System.out.println(customizedCase);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

PhoneCase 类作为原型,通过 clone() 方法创建新的对象副本。

在这段代码中,拷贝机制是通过实现 Cloneable 接口和重写 clone() 方法来实现的。具体来说,以下是拷贝机制的工作原理:

  1. 实现 Cloneable 接口:Cloneable 接口是一个标记接口,它表明实现了该接口的类可以进行克隆。在 PhoneCase 类中,通过 implements Cloneable 声明该类支持克隆。
  2. 重写 clone() 方法:在 PhoneCase 类中,重写了 clone() 方法。该方法调用了 Object 类的 clone() 方法,这是一个本地方法,负责创建对象的浅拷贝。然后,通过强制类型转换将浅拷贝的对象返回。

在这里需要注意,clone() 方法返回的是一个浅拷贝。浅拷贝会复制对象的所有字段,但对于字段引用的对象,只会复制引用,而不会复制对象本身。因此,在浅拷贝的情况下,原始对象和克隆对象共享相同的引用对象,如果引用对象发生改变,会影响到原始对象和克隆对象。

在我们的例子中,由于 String 类型是不可变的,因此不会产生问题。但如果 PhoneCase 类包含可变对象的引用,那么可能需要实现深拷贝来确保克隆对象的独立性。

我们刚刚说,当一个类的字段包含可变对象时,特别是这些可变对象需要在原始对象和克隆对象之间保持独立状态时,就需要使用深拷贝。 

假设我们有一个 Address 类表示地址信息,其包含了一个可变的 List 对象。我们希望在克隆对象中创建一个新的 List 对象,以确保原始对象和克隆对象的地址列表是独立的。

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

class Address implements Cloneable {
    private List<String> lines;

    public Address() {
        this.lines = new ArrayList<>();
    }

    public Address(List<String> lines) {
        this.lines = new ArrayList<>(lines);
    }

    public void addLine(String line) {
        lines.add(line);
    }

    @Override
    public Address clone() throws CloneNotSupportedException {
        // Perform deep copy for the List object
        List<String> newLines = new ArrayList<>(this.lines);
        Address newAddress = (Address) super.clone();
        newAddress.setLines(newLines);
        return newAddress;
    }

    public List<String> getLines() {
        return lines;
    }

    public void setLines(List<String> lines) {
        this.lines = lines;
    }

    @Override
    public String toString() {
        return "Address{" +
                "lines=" + lines +
                '}';
    }
}

public class PrototypePatternDeepCopyExample {
    public static void main(String[] args) {
        try {
            Address originalAddress = new Address();
            originalAddress.addLine("123 Main Street");
            originalAddress.addLine("City");

            // Clone the original address
            Address clonedAddress = originalAddress.clone();

            // Modify the cloned address
            clonedAddress.addLine("Country");

            // Print original and cloned addresses
            System.out.println("Original Address: " + originalAddress);
            System.out.println("Cloned Address: " + clonedAddress);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

在这个例子中,Address 类实现了 Cloneable 接口并重写了 clone() 方法来实现深拷贝。在 clone() 方法中,我们对地址的 List 对象进行了深拷贝,即创建了一个新的 List 对象,并将原始对象中的元素复制到新的 List 中。这样,原始对象和克隆对象的地址列表就是独立的,对其中一个对象进行修改不会影响到另一个对象。

核心思想

原型模式是一种创建型设计模式,其核心思想是基于现有对象创建新对象,而无需知道具体对象的类型或构造过程。这种模式将对象的创建和使用分离,使得对象的创建过程更加灵活和动态。

在原型模式中,通常会定义一个原型抽象类或接口,其中包含一个通用的克隆方法。具体的原型类实现了这个接口,并提供了对象的克隆方法。当需要创建新对象时,客户端代码可以通过调用原型对象的克隆方法来复制对象,而不是通过实例化类。

我们可以根据需要选择不同的克隆方式,例如浅拷贝或深拷贝。这种灵活性使得原型模式在一些情况下比直接实例化类更加方便和有效。

原型模式可以分为浅拷贝和深拷贝两种方式:

  1. 浅拷贝:通过复制对象的字段,创建一个新对象。如果字段是基本类型,则直接复制其值;如果字段是引用类型,则复制其引用,而不是对象本身。这意味着原始对象和克隆对象共享相同的引用对象。因此,对于引用类型的字段,如果修改了其中一个对象的引用对象,另一个对象的对应字段也会受到影响。

  2. 深拷贝:在复制对象时,不仅复制对象本身,还会递归复制其所有引用对象,直到所有对象都被复制为止。这样可以确保原始对象和克隆对象之间的所有引用都是相互独立的,修改其中一个对象不会影响另一个对象。

总的来说,原型模式的核心思想是通过复制现有对象来创建新对象,从而避免了直接依赖于特定类的实例化过程,提高了对象创建的灵活性和动态性。 

适用场景

原型模式通常适用于以下场景:

  1. 当需要创建的对象与已有对象类似,但又不希望直接依赖于特定类的实例化过程时,可以考虑使用原型模式。这种情况下,可以通过复制现有对象来创建新对象,而无需知道具体对象的类型或构造过程。

  2. 当对象的创建过程较为复杂,包括多个步骤或涉及到大量的初始化操作时,可以考虑使用原型模式。通过复制现有对象,可以避免重复执行对象的初始化操作,提高了对象创建的效率和性能。

  3. 当需要动态地创建对象,并且需要根据具体情况选择不同的创建方式时,可以考虑使用原型模式。通过定义不同的原型对象,并提供不同的克隆方法,可以根据需要选择合适的原型对象进行复制,从而实现动态创建对象的目的。

  4. 当对象的创建过程涉及到大量的资源消耗或性能开销较大时,可以考虑使用原型模式。通过复制现有对象,可以避免重复执行资源消耗较大的初始化操作,从而提高了系统的性能和效率。

  5. 当需要保持对象的内部状态一致性,并且希望对象之间的状态相互独立时,可以考虑使用原型模式。通过深拷贝方式创建新对象,可以确保对象之间的状态是相互独立的,修改一个对象的状态不会影响到其他对象的状态。

原型模式适用于需要动态地创建对象,并且对象之间存在相似性或依赖关系的情况,可以有效地提高系统的灵活性和可扩展性。

优缺点

原型模式(Prototype Pattern)的优点和缺点如下:

优点:

  1. 减少了对象的创建时间:原型模式通过复制现有对象来创建新对象,避免了对象的初始化过程,因此可以大大减少对象的创建时间,提高了系统的性能和效率。

  2. 提高了系统的灵活性:原型模式允许动态地创建对象,并且可以根据具体需求选择合适的原型对象进行复制,从而实现了动态创建对象的目的,提高了系统的灵活性和可扩展性。

  3. 简化了对象的创建过程:原型模式将对象的创建过程封装在原型对象中,客户端只需要通过复制原型对象来创建新对象,无需关心具体对象的类型或创建过程,使得对象的创建过程变得简单和统一。

  4. 支持动态配置对象:原型模式允许在运行时动态地改变对象的类型和属性,可以根据具体需求选择不同的原型对象进行复制,从而实现了对象的动态配置,提高了系统的灵活性和可维护性。

  5. 可以保持对象的内部状态一致性:原型模式通过深拷贝方式创建新对象,可以确保新对象与原型对象之间的状态是相互独立的,修改一个对象的状态不会影响到其他对象的状态,保持了对象的内部状态一致性。

缺点:

  1. 需要为每一个具体原型类创建克隆方法:在实现原型模式时,需要为每一个具体原型类都提供克隆方法,如果系统中存在大量的具体原型类,可能会导致代码的臃肿和复杂性增加。

  2. 深拷贝可能较为复杂:如果对象的内部结构较为复杂,深拷贝过程可能会比较复杂,需要递归地复制对象的所有成员变量,可能会增加系统的开销和复杂性。

  3. 需要注意深拷贝和浅拷贝的区别:在使用原型模式时,需要注意对象的拷贝方式,如果使用浅拷贝方式创建新对象,可能会导致对象之间的状态共享,从而破坏了对象的内部状态一致性。

  4. 可能会导致循环引用问题:如果对象之间存在循环引用关系,在进行深拷贝时可能会导致无限循环,从而导致系统的崩溃或内存溢出问题,需要特别注意。

总的来说,原型模式通过复制现有对象来创建新对象,具有创建时间短、灵活性高、简化对象创建过程等优点,但是需要注意深拷贝和浅拷贝的区别,以及可能存在的循环引用问题。

补充说明:

在 Java 中使用原型模式(Prototype Pattern)时,需要注意一些重要事项:

  1. 构造函数不会被执行: 当使用 clone() 方法进行对象拷贝时,被拷贝对象的构造函数不会被执行。即使类实现了 Cloneable 接口并重写了 clone() 方法,拷贝过程也不会调用类的构造函数。

  2. 浅拷贝与深拷贝: 在 Java 中,Object 类提供的 clone() 方法执行的是浅拷贝,即仅复制对象本身,而不会复制对象内部的引用对象。如果对象内部包含可变对象(如数组、集合等),则拷贝后的对象仍然与原始对象共享相同的引用对象,这可能导致对一个对象的修改影响到另一个对象。要实现深拷贝,需要手动处理引用对象的拷贝。

  3. 深拷贝的实现: 深拷贝可以通过手动复制对象内部的引用对象来实现,确保拷贝后的对象与原始对象完全独立。另一种实现深拷贝的方式是通过序列化和反序列化,将对象写入字节流再读取为新对象,这种方式可以自动处理对象内部的引用对象。

  4. 不要混合使用深拷贝和浅拷贝: 在设计中,不建议混合使用深拷贝和浅拷贝。这样的设计会增加代码的复杂性,特别是在涉及到类的继承和复杂对象关系时,容易引入不必要的错误和混淆。

  5. Clone 与 final 冲突: 对象的 clone() 方法与对象内部的 final 属性是有冲突的。如果类中的某些属性声明为 final,则不能直接使用 clone() 方法进行拷贝。解决方法是删除属性的 final 关键字,或者采用其他方式实现对象的复制。

综上所述,当使用原型模式时,需要谨慎考虑对象拷贝的方式以及可能的副作用,同时确保设计的简洁性和清晰性。

在实际项目中,原型模式往往与工厂方法模式结合使用,以便更灵活地创建对象并提供给调用者。这种组合可以带来一些优势,例如:

  1. 灵活性:原型模式允许在运行时动态地创建对象的副本,而无需依赖具体的类。结合工厂方法模式,可以根据需要选择合适的原型对象进行克隆,从而实现更灵活的对象创建和管理。

  2. 性能优化:当对象的创建过程比较复杂或耗时时,原型模式可以帮助提高性能。通过克隆已有对象来创建新对象,避免了重复的初始化过程,节省了时间和资源。

  3. 隐藏实现细节:工厂方法模式可以封装对象的创建逻辑,使调用者无需关心对象的具体创建过程。结合原型模式,工厂方法可以简单地调用原型对象的克隆方法来获取新对象,进一步隐藏了实现细节。

  4. 支持多态:工厂方法模式通过抽象工厂接口和具体工厂类来实现对象的创建,这样可以根据需要动态地选择不同的工厂类。结合原型模式,每个具体工厂类可以选择不同的原型对象进行克隆,从而实现多态的对象创建。

原型模式与工厂方法模式的结合可以提供更灵活、高效和可扩展的对象创建方案,适用于需要动态创建对象并隐藏创建细节的场景,是实际项目中常见的设计模式组合之一。

中介者模式【Mediator Pattern】

定义

中介者模式(Mediator Pattern)是一种行为设计模式,它允许将对象之间的通信集中处理,而不是每个对象之间直接相互通信。中介者模式通过引入中介者对象来解耦对象之间的交互,从而降低对象之间的耦合性,并使系统更易于维护和扩展。

在星型网络拓扑中,每个计算机通过交换机与其他计算机进行数据交换,形成了一种简单而稳定的网络结构。这种拓扑结构下,各个计算机之间并不直接进行通信,而是通过交换机进行中转和管理。只要中间的交换机正常运行,整个网络就能够正常工作,即使某个计算机出现故障也不会影响到整个网络的稳定性。

在现实生活中,公司、网吧等场所普遍采用星型网络,这是因为星型拓扑具有搭建简单、维护方便以及稳定可靠的特点,深受用户的青睐。

类似地,我们可以将星型网络拓扑中的中介者结构引入到我们的软件设计中,这就是中介者模式的由来。中介者模式通过引入一个中介者对象,将系统中的各个对象之间的直接通信转变为间接通信,从而降低了对象之间的耦合度,提高了系统的灵活性和可维护性。这种设计思想与星型网络的运作方式相似,都体现了一种简单、稳定、易于维护的设计理念。因此,将中介者模式应用于软件设计中,可以使系统结构更加清晰、稳定,易于理解和维护,从而更好地满足用户的需求。

 

具体来说,中介者模式包含以下关键角色:

  1. 中介者(Mediator):中介者是一个接口或抽象类,定义了对象之间交互的方法。中介者通常包含一个或多个方法,用于处理对象之间的通信、协调对象的行为,并对对象之间的关系进行管理。

  2. 具体中介者(Concrete Mediator):具体中介者是中介者接口的实现类,负责实际处理对象之间的通信和协调工作。具体中介者通常会维护一个对象之间的引用列表,用于管理和调度对象之间的交互。

  3. 同事类(Colleague):同事类是指需要相互交互的对象,它们通常包含一些业务逻辑和状态,并通过中介者进行通信。同事类通常持有一个中介者对象的引用,通过中介者来发送消息或接收消息。

  4. 具体同事类(Concrete Colleague):具体同事类是同事类的实现类,它们实际参与到对象之间的交互中。具体同事类通常会实现中介者定义的方法,并通过中介者来与其他同事类进行通信。

另外,在中介者模式中,同事(Colleague)角色是指参与到对象间通信的各个对象。每个同事对象都知道中介者对象,并且在与其他同事对象通信时,必须通过中介者对象进行协作。

同事类的行为可以分为两种:

  1. 自发行为(Self-Method):这种行为是指同事对象本身的行为,例如改变对象本身的状态、处理自己的行为等。自发行为与其他同事对象或中介者对象没有任何依赖关系,同事对象可以自行完成这些行为。

  2. 依赖方法(Dep-Method):这种行为是指同事对象必须依赖中介者对象才能完成的行为。在执行依赖方法时,同事对象需要通过中介者对象来协调和处理与其他同事对象之间的通信和交互,因此这些行为需要通过中介者对象来完成。

通过区分自发行为和依赖方法,同事对象可以清晰地知道哪些行为可以自行完成,哪些行为需要通过中介者来进行协调。

中介者模式在实际项目中的应用通常涉及多个私有方法的定义,这些私有方法旨在处理对象之间的依赖关系。这意味着原本需要一个对象依赖多个对象的情况,现在可以通过中介者的私有方法来处理。在实践中,通常会按照职责划分中介者,使每个中介者负责处理一个或多个类似的关联请求。

具体来说,在场景类中引入了一个中介者,并将其分别传递给三个同事类。这些同事类都具有相似的特性:它们只处理自己相关的活动(行为),对于与自己无关的活动则委托给中介者处理。无论使用哪个同事类,程序的运行结果都是相同的。

从项目设计的角度来看,引入中介者后,设计结构变得更加清晰,对象之间的耦合性大大降低,代码的质量也得到了显著提升。

总之,中介者模式的核心思想是将系统中对象之间的复杂交互关系集中到一个中介者对象中,使得各个对象之间的通信变得简单明了。中介者模式在处理多个对象依赖的情况下发挥了重要作用。通过引入中介者角色,取消了多个对象之间的直接关联或依赖关系,从而降低了对象之间的耦合性,提高了系统的灵活性和可维护性。

举例说明

假设有一个富有创意的团队正在策划一场盛大的主题派对,派对上将有各种各样的活动和游戏,包括音乐表演、小游戏、抽奖等等。团队中的每个成员都负责组织一项活动,而且这些活动之间可能会相互影响,例如音乐表演需要适时停止以进行抽奖环节等等。

在没有中介者模式的情况下,每个团队成员可能会直接与其他成员沟通,试图协调各自的活动。这样做可能会导致沟通混乱、活动冲突和时间安排不协调的问题。

为了解决这个问题,他们决定引入一个中介者,也就是一个派对策划师。派对策划师负责协调所有活动,并在必要时进行调整。例如,如果音乐表演需要暂停以进行抽奖环节,派对策划师会通知相关团队成员。如果有某个活动需要延迟或提前进行,派对策划师也会进行相应的调整。

通过引入派对策划师作为中介者,团队成员之间不再直接沟通,而是通过派对策划师进行协调和通信。这样一来,团队成员可以更专注于各自的活动,而不必担心其他活动可能带来的影响。同时,派对策划师也可以更好地掌控整个派对的节奏和流程,确保活动顺利进行。

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

// 活动接口
interface Activity {
    void pause();
    void stop();
}

// 音乐表演
class MusicPerformance implements Activity {
    private PartyPlannerMediator mediator;

    public MusicPerformance(PartyPlannerMediator mediator) {
        this.mediator = mediator;
        this.mediator.addActivity(this);
    }

    @Override
    public void pause() {
        System.out.println("音乐表演暂停!");
    }

    @Override
    public void stop() {
        System.out.println("音乐表演结束!");
    }

    // 音乐表演特有的方法
    public void start() {
        System.out.println("音乐表演开始!");
        // 通知中介者进行协调
        mediator.coordinateActivities(this);
    }
}

// 抽奖环节
class Lottery implements Activity {
    private PartyPlannerMediator mediator;

    public Lottery(PartyPlannerMediator mediator) {
        this.mediator = mediator;
        this.mediator.addActivity(this);
    }

    @Override
    public void pause() {
        System.out.println("抽奖环节暂停!");
    }

    @Override
    public void stop() {
        System.out.println("抽奖环节结束!");
    }

    // 抽奖环节特有的方法
    public void start() {
        System.out.println("抽奖环节开始!");
        // 通知中介者进行协调
        mediator.coordinateActivities(this);
    }
}

// 舞蹈表演
class Dancing implements Activity {
    private PartyPlannerMediator mediator;

    public Dancing(PartyPlannerMediator mediator) {
        this.mediator = mediator;
        this.mediator.addActivity(this);
    }

    @Override
    public void pause() {
        System.out.println("舞蹈表演暂停!");
    }

    @Override
    public void stop() {
        System.out.println("舞蹈表演结束!");
    }
}

// 中介者接口
interface PartyPlannerMediator {
    void addActivity(Activity activity);
    void coordinateActivities(Activity changedActivity);
}

// 派对策划师作为中介者
class PartyPlanner implements PartyPlannerMediator {
    private List<Activity> activities;

    public PartyPlanner() {
        this.activities = new ArrayList<>();
    }

    @Override
    public void addActivity(Activity activity) {
        activities.add(activity);
    }

    @Override
    public void coordinateActivities(Activity changedActivity) {
        if (changedActivity instanceof MusicPerformance) {
            // 如果是音乐表演,暂停其他活动进行抽奖环节
            System.out.println("音乐表演即将开始,暂停其他活动进行抽奖环节!");
            for (Activity activity : activities) {
                if (activity != changedActivity) {
                    activity.pause();
                }
            }
        } else if (changedActivity instanceof Lottery) {
            // 如果是抽奖环节,结束音乐表演
            System.out.println("抽奖环节开始,结束音乐表演!");
            for (Activity activity : activities) {
                if (activity instanceof MusicPerformance) {
                    activity.stop();
                }
            }
        }
    }
}

public class MediatorPartyExample {
    public static void main(String[] args) {
        // 创建派对策划师中介者
        PartyPlannerMediator mediator = new PartyPlanner();

        // 创建音乐表演和抽奖环节
        MusicPerformance musicPerformance = new MusicPerformance(mediator);
        Lottery lottery = new Lottery(mediator);

        // 派对开始
        System.out.println("派对开始!");

        // 音乐表演开始
        musicPerformance.start();

        // 其他派对活动
        Dancing dancing = new Dancing(mediator);
        drinkingGame(mediator);

        // 抽奖环节开始
        lottery.start();

        // 派对结束
        System.out.println("派对结束!");
    }

    // 其他派对活动
    private static void drinkingGame(PartyPlannerMediator mediator) {
        System.out.println("参加一些喝酒游戏!");
    }
}

核心思想

中介者模式(Mediator Pattern)的核心思想是将对象之间的直接交互转变为通过中介者对象间接进行通信。在传统的对象间通信方式中,对象之间可能会相互引用,导致耦合度较高,难以维护和扩展。而中介者模式通过引入中介者对象,使得对象之间不再直接相互引用,而是通过中介者对象进行通信,从而降低了对象之间的耦合性。

中介者模式,也称为调停者模式,中介者模式的作用就像是在这场混乱的战争中添加了一个中心指挥部,所有的对象都与中心进行通信,而不再直接与其他对象交流。这个中心就像是调停者,负责管理和协调各个对象之间的交互。当某个对象需要与其他对象进行通信时,它只需将消息发送给中心,由中心来决定如何处理这些消息,以及如何将消息传递给其他相关的对象。

中介者模式的核心思想可以总结为以下几点:

  1. 解耦对象之间的交互:中介者模式通过引入中介者对象,将对象之间的交互集中到中介者对象中处理。这样一来,对象之间不再直接相互引用,减少了对象之间的依赖关系,降低了耦合度。

  2. 集中控制交互逻辑:中介者模式将对象之间的交互逻辑集中在中介者对象中实现,而不是分散在各个对象中。中介者对象负责协调对象之间的通信和行为,使得系统更加清晰和易于理解。

  3. 促进复杂系统的维护和扩展:通过中介者模式,可以将复杂系统分解为多个独立的模块,并通过中介者对象进行协调。这样一来,系统的各个模块之间的关系更加清晰,便于维护和扩展。

  4. 降低对象间的直接耦合:由于对象之间不再直接相互引用,而是通过中介者对象进行通信,因此可以降低对象之间的直接耦合。这使得系统更加灵活,可以更容易地修改和替换对象。

总之,中介者模式的核心思想是通过引入中介者对象,将对象之间的交互集中处理,从而降低系统的耦合度,提高系统的灵活性和可维护性。

适用场景

中介者模式适用于以下情况:

  1. 对象之间存在复杂的交互关系: 当系统中的对象之间存在复杂的交互关系,且对象之间的通信方式多种多样时,可以考虑使用中介者模式。中介者模式可以将对象之间的交互逻辑集中到中介者对象中,使得系统更加清晰和易于理解。

  2. 对象之间的耦合度较高: 当系统中的对象之间存在较高的耦合度,修改一个对象可能会影响到其他多个对象时,可以考虑使用中介者模式。中介者模式可以降低对象之间的直接耦合,使得系统更加灵活和可维护。

  3. 需要动态改变对象之间的交互行为: 当系统中的对象之间的交互行为需要根据不同的情况动态改变时,可以考虑使用中介者模式。中介者模式可以将对象之间的交互行为集中在中介者对象中,使得交互行为更加灵活和可配置。

  4. 对象间的通信复杂度高: 当系统中的对象之间的通信方式较为复杂,对象之间需要频繁地进行通信时,可以考虑使用中介者模式。中介者模式可以简化对象之间的通信过程,提高系统的可维护性和可扩展性。

  5. 系统中的对象数量较多: 当系统中的对象数量较多,对象之间的关系错综复杂时,可以考虑使用中介者模式。中介者模式可以将系统中的对象分解为多个独立的模块,并通过中介者对象进行协调,使得系统更加清晰和易于理解。

  6. 产品开发和框架设计: 中介者模式在产品开发和框架设计中也经常被使用。例如,MVC(Model-View-Controller)框架就是一个典型的中介者模式的应用。在产品开发中,使用中介者模式可以提升产品的性能、可扩展性和稳定性,因为它能够更好地管理和协调系统中的各个组件和模块。

在何种情况下应该使用中介者模式是一个关键问题。尽管中介者模式能够解决一些复杂的对象之间的交互问题,但过度使用中介者模式可能会导致中介者的膨胀问题,增加系统的复杂性。因此,在项目开发中,需要谨慎评估是否真正需要中介者模式,以避免过度设计和不必要的复杂性。

优缺点

中介者模式(Mediator Pattern)具有以下优点:

  1. 降低对象之间的耦合度:中介者模式通过引入中介者对象来协调对象之间的通信,使得对象之间不再直接相互引用,从而降低了对象之间的耦合度。这使得系统更加灵活,可以更容易地修改和替换对象。

  2. 集中控制交互逻辑:中介者模式将对象之间的交互逻辑集中在中介者对象中实现,而不是分散在各个对象中。这样一来,系统的交互逻辑更加清晰和易于理解,便于维护和扩展。

  3. 促进复杂系统的维护和扩展:通过中介者模式,可以将复杂系统分解为多个独立的模块,并通过中介者对象进行协调。这样一来,系统的各个模块之间的关系更加清晰,便于维护和扩展。

  4. 减少对象间的直接通信:中介者模式可以避免对象之间的直接通信,所有的交互都通过中介者对象进行,从而降低了对象之间的依赖关系,减少了耦合度。

  5. 提高系统的灵活性和可扩展性:由于对象之间的通信逻辑集中在中介者对象中,因此可以更轻松地修改和扩展系统的功能,不会影响到其他对象。

中介者模式的缺点包括:

  1. 中介者对象的复杂性:随着系统的增长,中介者对象可能会变得复杂,包含大量的交互逻辑。这可能会导致中介者对象的维护和理解成本增加。

  2. 增加了系统的单点故障:由于所有的对象之间的通信都通过中介者对象进行,因此中介者对象成为系统的单点故障。如果中介者对象出现问题,可能会影响到整个系统的运行。

  3. 可能导致性能问题:中介者模式将所有对象之间的通信都集中在一个对象中处理,可能会导致中介者对象成为系统的瓶颈,影响系统的性能。

综上所述,中介者模式可以降低对象之间的耦合度,提高系统的灵活性和可维护性,但也需要注意中介者对象的复杂性、单点故障和性能问题。在设计时需要权衡利弊,根据具体情况选择是否使用中介者模式。

补充说明:

中介者模式是一种简单却容易被误用的设计模式。尽管它看起来简单明了,但在实际应用中,很容易陷入误区。在面向对象编程中,对象之间的依赖关系是不可避免的,因此并非每个场景都适合使用中介者模式。

首先,需要明确的是,如果一个类与其他类没有任何依赖关系,或者其他类也不依赖于这个类,那么这个类在项目中可能就没有存在的必要。类似于现实生活中的社会关系,人们之间的相互依赖是自然而然的,孤立存在的情况并不常见。

其次,尽管存在多个对象之间的复杂依赖关系,但并不是每种情况都适合使用中介者模式。中介者模式的引入必须具有明确的目的,而不是为了使用模式而使用模式。不正确地使用中介者模式可能导致中介者对象的逻辑变得复杂,从而使原本简单的对象之间的依赖关系变得更加混乱。

因此,中介者模式适用于多个对象之间紧密耦合、依赖关系错综复杂的情况。一般来说,当类图中出现了蜘蛛网状结构时,就可以考虑使用中介者模式。在这种情况下,中介者模式有助于将复杂的关系整理成一个清晰简单的星型结构,从而提高系统的可维护性和灵活性。

综上所述,中介者模式的使用需要慎重考虑,必须根据具体情况量力而行。只有在对象之间的依赖关系较为复杂、耦合度较高时,才适合考虑使用中介者模式。在正确的情况下使用中介者模式可以简化系统的设计,提高代码的质量,但不适当地使用可能会带来不必要的复杂性。

依赖倒转原则(Dependency Inversion Principle,简称DIP)是面向对象设计原则之一,它是SOLID设计原则中的其中一个。依赖倒转原则主要有两个核心概念:

  1. 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
  2. 抽象不应该依赖于细节,细节应该依赖于抽象。

简而言之,依赖倒转原则强调的是要依赖于抽象而不是具体实现。这意味着在设计中,应该通过接口、抽象类等抽象层来进行依赖管理,而不是直接依赖于具体的实现类。

通过遵循依赖倒转原则,可以实现以下好处:

  1. 减少模块之间的耦合性:通过依赖抽象而不是具体实现,可以降低模块之间的耦合度,提高系统的灵活性和可维护性。
  2. 提高代码的可扩展性:由于高层模块不依赖于低层模块的具体实现,因此可以更轻松地替换或扩展模块。
  3. 促进代码的复用:通过依赖于抽象,可以更容易地在不同的上下文中重用代码。

中介者模式很少使用接口或者抽象类的情况确实与依赖倒转原则存在冲突。这是因为中介者模式的设计理念与依赖倒转原则的核心思想并不完全契合。

首先,对于同事类而言,它们之间是协作关系而不是继承关系。每个同事类可能执行不同的任务,处理不同的业务,因此在抽象类或接口中严格定义同事类必须具有的方法并不合适。虽然一些基本的行为或属性(如吃饭、上厕所)可以被抽象出来,但这些行为通常并不是中介者模式所关心的,因此在抽象中定义它们可能是不合适的。如果两个对象之间没有明显的共性,强行将它们的行为抽象出来也是不必要的,抽象应该只定义出模式需要的角色。

其次,对于中介者而言,一个项目中可能存在多个模块采用中介者模式,每个中介者所围绕的同事类可能都不相同。因此,很难抽象出一个具有共性的中介者接口或抽象类。通常情况下,一个中介者抽象类可能只有一个实现者,除非中介者的逻辑非常复杂,代码量很大,才会考虑使用多个中介者的情况。因此,对于中介者来说,抽象的必要性就不太明显了。

综上所述,中介者模式中很少使用接口或抽象类的原因主要是因为在同事类和中介者类之间很难找到共性,以及中介者模式的灵活性要求。因此,通常情况下,我们更倾向于根据具体情况来设计同事类和中介者类,而不是强行引入抽象。这样可以更好地满足系统的需求,并避免不必要的复杂性。

解释器模式【Interpreter Pattern】

定义

解释器模式是一种行为型设计模式,用于定义一门语言的文法,并解释该语言中的表达式。它通过定义一个解释器来解释语言中的表达式,从而实现对表达式的解析和执行。解释器模式通常涉及以下角色:

  1. 抽象表达式(Abstract Expression):定义了一个抽象的解释操作接口,通常包含一个 interpret() 方法,用于解释语言中的表达式。

  2. 终结符表达式(Terminal Expression):实现了抽象表达式接口,并表示语言中的终结符,即不再包含其他子表达式的表达式。

  3. 非终结符表达式(Nonterminal Expression):实现了抽象表达式接口,并表示语言中的非终结符,即包含其他子表达式的表达式。

  4. 上下文(Context):包含解释器解释的全局信息或状态。

  5. 客户端(Client):创建并配置具体的解释器,并使用解释器解释语言中的表达式。

 

解释器模式适用于以下情况:

  • 当需要解释和执行一种特定语言的表达式时,可以使用解释器模式。
  • 当需要实现一种语言的文法,并能够灵活地解释和执行该语言中的表达式时,解释器模式是一种很好的选择。
  • 当需要对一组语法规则进行类层次结构化表示,并能够动态地对其进行解释时,解释器模式可以发挥作用。

总的来说,解释器模式提供了一种灵活的方式来解释和执行特定语言的表达式,使得我们能够轻松地扩展和修改语言的文法,同时降低了解释和执行过程的复杂性

举例说明

假设我们有一个智能语音助手,它能够理解用户输入的自然语言指令,并执行相应的操作。我们以一个简单的智能音乐播放器为例来解释解释器模式。

在这个场景中,用户可以通过语音与小豆豆交互,例如:

  • 用户说:“播放一首轻松的音乐。”
  • 用户说:“暂停音乐。”
  • 用户说:“下一曲。”
  • 用户说:“停止音乐。”

现在,我们使用解释器模式来实现这个智能音乐播放器。以下是角色的定义:

  1. 抽象表达式(Abstract Expression): 这个角色定义了解释器的接口,通常包含一个解释方法,用于解释用户输入的命令。

  2. 终结符表达式(Terminal Expression): 终结符表达式是具体的解释器类,实现了抽象表达式接口。它们用于解释和执行具体的命令,例如播放音乐、暂停音乐、下一曲、停止音乐等。

  3. 非终结符表达式(Non-terminal Expression): 这个角色也是具体的解释器类,用于组合和解释多个终结符表达式,以实现复杂的语法规则。例如,可以将多个命令组合成一个复合命令。

  4. 环境类(Context): 环境类包含解释器需要的信息或全局状态,并将这些信息传递给解释器执行解释操作。它负责接收用户输入的自然语言指令,并将指令传递给解释器进行解释和执行。

通过解释器模式,我们能够将用户输入的自然语言指令转换为系统可执行的操作,实现了智能语音助手与用户之间的交互。这种模式使得系统具有了灵活性和可扩展性,能够方便地添加新的指令和功能,提升用户体验。

import java.util.HashMap;
import java.util.Map;

// 抽象表达式接口
interface Expression {
    void interpret(String context);
}

// 终结符表达式:播放音乐
class PlayMusicExpression implements Expression {
    @Override
    public void interpret(String context) {
        System.out.println("正在播放音乐");
    }
}

// 终结符表达式:暂停音乐
class PauseMusicExpression implements Expression {
    @Override
    public void interpret(String context) {
        System.out.println("正在暂停音乐");
    }
}

// 终结符表达式:下一曲
class NextSongExpression implements Expression {
    @Override
    public void interpret(String context) {
        System.out.println("切换到下一曲");
    }
}

// 终结符表达式:停止音乐
class StopMusicExpression implements Expression {
    @Override
    public void interpret(String context) {
        System.out.println("停止播放音乐");
    }
}

// 非终结符表达式:播放列表
class PlaylistExpression implements Expression {
    private Expression[] expressions;

    public PlaylistExpression(Expression... expressions) {
        this.expressions = expressions;
    }

    @Override
    public void interpret(String context) {
        System.out.println("正在播放播放列表:" + context);
        for (Expression expression : expressions) {
            expression.interpret("");
        }
    }
}

// 环境类:包含解释器需要的信息或全局状态
class Context {
    private Map<String, Expression> expressions = new HashMap<>();

    public Context() {
        expressions.put("播放音乐", new PlayMusicExpression());
        expressions.put("暂停音乐", new PauseMusicExpression());
        expressions.put("下一曲", new NextSongExpression());
        expressions.put("停止音乐", new StopMusicExpression());
    }

    // 解释器方法
    public void interpret(String command) {
        Expression expression = expressions.get(command);
        if (expression != null) {
            expression.interpret(command);
        } else {
            System.out.println("对不起,无法识别的命令:" + command);
        }
    }
}

// 客户端
public class InterpreterPatternExample {
    public static void main(String[] args) {
        // 创建环境类
        Context context = new Context();

        // 用户输入命令
        String[] commands = {"播放音乐", "暂停音乐", "下一曲", "停止音乐","调高音量"};

        // 解释和执行命令
        for (String command : commands) {
            context.interpret(command);
        }

        // 创建一个播放列表命令
        Expression playlistExpression = new PlaylistExpression(
                new PlayMusicExpression(), new NextSongExpression(), new PauseMusicExpression());

        // 解释和执行播放列表命令
        playlistExpression.interpret("我的最爱");
    }
}
  • Expression 接口定义了解释器的抽象方法 interpret。
  • 每个具体的终结符表达式(如 PlayMusicExpression、PauseMusicExpression 等)实现了 Expression 接口,并根据具体的命令执行相应的操作。
  • Context 类包含了一个命令与具体终结符表达式的映射关系,以及一个解释器方法 interpret,用于根据用户输入的命令执行相应的操作。
  • 在客户端代码中,我们创建了一个 Context 对象,并传入用户输入的命令,通过调用 interpret 方法来解释和执行命令。
  • 添加了一个名为 PlaylistExpression 的非终结符表达式类,它接受多个终结符表达式作为参数,并在解释时依次执行这些终结符表达式。这样,我们就能够支持更复杂的命令解析,比如播放列表的命令。

 

核心思想

解释器模式的核心思想是定义一种语言的文法,并提供一种方式来解释和执行该语言中的表达式。它通过定义一组解释器来解释语言中的各种表达式,从而实现对表达式的解析和执行。

核心思想主要如下:

  1. 定义语言的文法: 首先,需要定义特定语言的文法,包括语言中的各种表达式和规则。这些表达式可以是终结符(Terminal Expression)和非终结符(Nonterminal Expression)。

  2. 创建解释器: 根据语言的文法,创建相应的解释器来解释和执行表达式。解释器根据不同的文法规则,定义了解释和执行表达式的方式。

  3. 解释表达式: 当需要解释和执行语言中的表达式时,将表达式传递给相应的解释器进行解释。解释器根据表达式的类型和规则,解释并执行表达式,从而实现对语言的解析和执行。

  4. 灵活性和扩展性: 解释器模式提供了一种灵活的方式来解释和执行特定语言的表达式,使得可以轻松地扩展和修改语言的文法,从而满足不同的需求。

总之,解释器模式的核心思想是通过定义一组解释器来解释和执行特定语言的表达式,从而实现对语言的解析和执行。这种模式适用于需要解释和执行特定语言的场景,可以提供一种灵活的方式来处理语言中的各种表达式和规则。

适用场景

解释器模式适用于以下场景:

  1. 需要解释和执行特定语言的表达式: 当系统中存在需要解释和执行特定语言的表达式时,可以使用解释器模式。这种情况下,解释器模式可以帮助将语言的表达式解析和执行转化为代码实现。

  2. 复杂的语法规则: 当语言的语法规则非常复杂且多样化时,可以使用解释器模式。解释器模式可以通过定义一组解释器来解释和执行语言中的各种表达式和规则,从而简化系统的设计和实现。

  3. 频繁变化的表达式: 当系统中的表达式需要频繁变化或动态生成时,可以使用解释器模式。解释器模式可以提供一种灵活的方式来解释和执行表达式,使得可以轻松地根据需要修改和调整表达式的规则。

  4. 代码重用性: 当系统中存在大量重复的代码逻辑时,可以使用解释器模式。通过定义一组通用的解释器来解释和执行表达式,可以实现代码的重用,提高系统的可维护性和可扩展性。

  5. 文法相对稳定: 当语言的文法相对稳定且不经常变化时,可以使用解释器模式。解释器模式适合用于解析和执行相对固定的语言文法,而不适用于频繁变化的文法。

解释器模式适用于需要解释和执行特定语言的表达式、复杂的语法规则、频繁变化的表达式、代码重用性高以及文法相对稳定的场景。通过使用解释器模式,可以实现对语言的解析和执行,提高系统的灵活性和可维护性。

优缺点

解释器模式的优点:

  1. 灵活性: 解释器模式可以灵活地扩展和修改语言的文法规则和解释方式,使得系统更加灵活和可扩展。

  2. 可维护性: 解释器模式将语言的文法规则和解释逻辑封装在解释器中,使得系统的结构更加清晰,易于理解和维护。

  3. 可复用性: 解释器模式可以通过定义一组通用的解释器来解释和执行不同的表达式和规则,实现代码的复用。

  4. 扩展性: 解释器模式可以通过扩展和修改解释器来支持新的语言特性和表达式类型,使得系统更加灵活和可扩展。

  5. 简化设计: 解释器模式将复杂的语法规则和解释逻辑封装在解释器中,使得系统的设计更加简化,易于实现和维护。

解释器模式的缺点:

  1. 性能问题: 解释器模式通常需要解释和执行大量的语言表达式,可能会导致性能问题,特别是在处理复杂和大规模的表达式时。

  2. 复杂度: 解释器模式引入了大量的解释器对象和解释逻辑,可能会导致系统的复杂度增加,降低系统的可读性和可维护性。

  3. 可扩展性受限: 解释器模式的可扩展性受限于已定义的解释器和语法规则,如果需要支持新的语言特性和表达式类型,可能需要修改和扩展解释器,增加系统的复杂度。

  4. 上下文相关性: 解释器模式通常依赖于上下文环境来执行解释逻辑,可能会受到上下文环境的影响,导致解释结果不一致或不准确。

总之,解释器模式适用于需要灵活解释和执行语言表达式的场景,但在性能、复杂度和可扩展性方面存在一定的缺点,需要综合考虑使用时的实际情况。

亨元模式【Flyweight Pattern】

定义

当系统中存在大量相似对象,而这些对象可以共享部分内部状态,但又具有各自不同的外部状态时,我们就可以使用享元模式。

享元模式(Flyweight Pattern) 是一种结构型设计模式,旨在通过共享对象来最大限度地减少系统中的对象数量,从而减少内存消耗和提高性能。该模式适用于存在大量相似对象但又不需要多个独立实例的情况,其中一部分状态可以被共享。享元模式通过将对象的共享部分提取出来,并在需要时将其动态添加到对象中,以实现对象的重复利用,从而减少内存开销。

在享元模式中,内部状态(Intrinsic State)和外部状态(Extrinsic State)是两种重要的状态概念,它们在共享对象和具体对象之间扮演着不同的角色:

  1. 内部状态(Intrinsic State)

    • 内部状态是享元对象所具有的可以被多个对象共享的状态。
    • 这些状态通常存储在享元对象内部,并在享元对象创建时确定,不会随环境的改变而改变。
    • 内部状态对于享元对象是固定的,所有引用该享元对象的对象共享相同的内部状态。
    • 内部状态的变化不会影响享元对象的外部行为或其他对象。
    • 由于内部状态是共享的,因此它可以被多个享元对象实例共享,从而节省内存和资源。
  2. 外部状态(Extrinsic State)

    • 外部状态是每个对象都具有的不同状态,通常由客户端在运行时传入,会影响对象的行为。
    • 这些状态不能被共享,每个对象都具有自己的外部状态,因此它们是特定于对象实例的。
    • 外部状态的变化会影响对象的行为,因此它是对象的重要属性之一。
    • 通过在运行时传入外部状态,客户端可以定制享元对象的行为,使其适应不同的上下文或需求。
    • 外部状态使得相同的享元对象可以以不同的方式被使用,从而增加了灵活性和可定制性。

通过内部状态和外部状态的结合使用,享元模式实现了对大量相似对象的高效共享,同时保留了对象的个性化特征和可定制性,从而在一定程度上提高了系统的性能和效率。

享元模式的关键角色包括:

  1. 抽象享元类(Flyweight)

    • 定义了享元对象的接口或抽象类,声明了需要接收外部状态的方法。
    • 包含了一个或多个用于接收外部状态的方法,这些方法可以是设置外部状态的操作,也可以是以外部状态作为参数的行为方法。
    • 抽象享元类可以包含一些共享的属性或方法,这些属性或方法是所有具体享元类的通用部分,也可以提供一些默认的行为。
  2. 具体享元类(Concrete Flyweight)

    • 实现了抽象享元类定义的接口,代表了可以被共享的具体对象。
    • 具体享元类通常包含内部状态,并负责处理内部状态的操作。
    • 内部状态是可以被共享的,因此具体享元类可以被多个客户端共享。
    • 当具体享元对象被请求时,如果存在相同的内部状态,则可以直接返回该对象;如果不存在,则创建一个新的具体享元对象。
  3. 非共享具体享元类

    • 一种特殊的具体享元类,表示不可共享的对象。
    • 这些对象无法在享元池中被共享,每个对象都是独立的实例。
    • 非共享具体享元类通常不包含内部状态,或者包含的内部状态是与其他对象不同的。
  4. 获取享元类的工厂类(Flyweight Factory)

    • 负责管理和创建享元对象。
    • 维护一个享元池来存储已经创建的享元对象,池中的对象是共享的。
    • 当客户端请求创建一个享元对象时,工厂类会首先查找是否已存在符合条件的享元对象,如果存在则直接返回;如果不存在,则创建一个新的享元对象并添加到享元池中。
    • 工厂类可以根据需求进行池的管理,例如设置对象的最大数量、定时清理等。

使用享元模式,系统可以在一定程度上减少内存消耗,并提高系统的性能和效率,特别适用于存在大量相似对象且对象之间有部分重复状态的场景。

举例说明

在游乐园中的弹球游戏区域,游客们可以尽情享受弹球游戏的乐趣。弹球游戏区域被设计成一个充满活力和刺激的地方,吸引着来自各个年龄段的游客。在这个区域里,有各种各样的弹球供游客选择,每个弹球都有其独特的特性和魅力。

在这个游戏区域中,游客们可以发现各种形状和颜色的弹球,有圆形的、方形的、五角星形的,以及红色、蓝色、黄色等各种丰富的颜色选择。此外,每个弹球还可能具有特殊的技能或功能,比如一些弹球可能会发出闪光效果,一些可能会产生音效,还有一些可能会在碰撞时释放出特殊效果。

然而,游乐园的资源是有限的,无法为每位游客提供独立的弹球。为了确保游戏区域的良好运营和资源的有效利用,游乐园管理者决定采用享元模式来管理弹球对象。

在这个场景中,游客们可以根据自己的喜好选择不同颜色和形状的弹球进行游戏。当游客选择一个特定的弹球时,弹球工厂会从弹球池中提供一个现有的共享弹球对象,如果池中没有符合条件的弹球,则会动态地创建一个新的弹球对象并添加到池中。这样,游客们可以尽情享受弹球游戏的乐趣,而不必担心资源的浪费和性能的下降。通过有效地共享和管理弹球对象,游乐园能够为游客们提供一个愉快而难忘的游戏体验。

import java.util.HashMap;
import java.util.Map;

// 弹球接口
interface Pinball {
    void display(int x, int y); // 显示弹球在游戏界面的位置
    void move(int deltaX, int deltaY); // 移动弹球的位置
    void play(); // 弹球开始游戏
}

// 具体享元类 - 弹球
class ConcretePinball implements Pinball {
    private String color;
    private String shape;

    public ConcretePinball(String color, String shape) {
        this.color = color;
        this.shape = shape;
    }

    @Override
    public void display(int x, int y) {
        System.out.println("Displaying " + color + " " + shape + " pinball at position (" + x + ", " + y + ")");
    }

    @Override
    public void move(int deltaX, int deltaY) {
        System.out.println("Moving " + color + " " + shape + " pinball by (" + deltaX + ", " + deltaY + ")");
    }

    @Override
    public void play() {
        System.out.println("Playing with " + color + " " + shape + " pinball!");
    }
}

// 享元工厂类 - 弹球工厂
class PinballFactory {
    private Map<String, Pinball> pinballPool = new HashMap<>();

    public Pinball getPinball(String color, String shape) {
        String key = color + "_" + shape;
        if (!pinballPool.containsKey(key)) {
            pinballPool.put(key, new ConcretePinball(color, shape));
        }
        return pinballPool.get(key);
    }
}

// 游客类
class Visitor {
    private String name;
    private Pinball pinball;

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

    public void choosePinball(String color, String shape, int x, int y) {
        PinballFactory factory = new PinballFactory();
        pinball = factory.getPinball(color, shape);
        pinball.display(x, y);
    }

    public void playWithPinball() {
        if (pinball != null) {
            pinball.play();
        } else {
            System.out.println("Please choose a pinball first!");
        }
    }
}

public class PinballGame {
    public static void main(String[] args) {
        // 创建游客
        Visitor visitor1 = new Visitor("Alice");
        Visitor visitor2 = new Visitor("Bob");
        Visitor visitor3 = new Visitor("Fidder");

        // 游客选择不同的弹球
        visitor1.choosePinball("Red", "Circle", 100, 50);
        visitor2.choosePinball("Blue", "Square", 200, 100);

        // 游客开始游戏
        visitor1.playWithPinball();
        visitor2.playWithPinball();
        visitor3.playWithPinball();
    }
}

  • 具体享元类:ConcretePinball 类实现了 Pinball 接口,代表了可以被共享的具体对象。在这个例子中,每个具体的弹球都是一个具体享元类的实例。这些弹球对象可以被多个游客共享。
  • 抽象享元类:Pinball 接口定义了享元对象的接口,通过该接口可以接收并作用于外部状态。它包含了 display()、move() 和 play() 等方法,这些方法是所有弹球对象的共同行为。具体享元类 ConcretePinball 实现了这个接口。
  • 非共享具体享元类:在这个示例中,我们没有明确的非共享具体享元类。因为所有的弹球都可以被多个游客共享。但是如果添加一些特殊的弹球,比如定制的或者个性化的弹球,这些弹球可能是非共享的,因为它们具有唯一的属性,无法被其他弹球共享。
  • 获取享元类的工厂类:PinballFactory 类是享元工厂,负责管理和创建享元对象。它维护一个享元池来存储已经创建的享元对象,并根据需要共享这些对象。当客户端请求创建一个享元对象时,享元工厂会首先查找是否已存在符合条件的享元对象,如果存在则直接返回;如果不存在,则创建一个新的享元对象并添加到享元池中。

让我们添加一个非共享的具体弹球类 CustomPinball,它代表了定制或个性化的弹球。同时再在Visitor类中新增movePinball方法,使游客可以移动已选择的普通弹球。这样,我们就可以更全面地测试弹球对象的移动行为: 

import java.util.HashMap;
import java.util.Map;

// 弹球接口
interface Pinball {
    void display(int x, int y); // 显示弹球在游戏界面的位置
    void move(int deltaX, int deltaY); // 移动弹球的位置
    void play(); // 弹球开始游戏
}

// 具体享元类 - 弹球
class ConcretePinball implements Pinball {
    private String color;
    private String shape;

    public ConcretePinball(String color, String shape) {
        this.color = color;
        this.shape = shape;
    }

    @Override
    public void display(int x, int y) {
        System.out.println("Displaying " + color + " " + shape + " pinball at position (" + x + ", " + y + ")");
    }

    @Override
    public void move(int deltaX, int deltaY) {
        System.out.println("Moving " + color + " " + shape + " pinball by (" + deltaX + ", " + deltaY + ")");
    }

    @Override
    public void play() {
        System.out.println("Playing with " + color + " " + shape + " pinball!");
    }
}

// 非共享具体享元类
class CustomPinball implements Pinball {
    private String color;
    private String shape;
    private String specialFeature;

    public CustomPinball(String color, String shape) {
        this.color = color;
        this.shape = shape;
        this.specialFeature = "Special Feature";
    }

    @Override
    public void display(int x, int y) {
        System.out.println("Displaying custom " + color + " " + shape + " pinball with special feature '" + specialFeature + "' at position (" + x + ", " + y + ")");
    }

    @Override
    public void move(int deltaX, int deltaY) {
        System.out.println("Moving custom " + color + " " + shape + " pinball with special feature '" + specialFeature + "' by (" + deltaX + ", " + deltaY + ")");
    }

    @Override
    public void play() {
        System.out.println("Playing with custom " + color + " " + shape + " pinball with special feature '" + specialFeature + "'!");
    }
}

// 享元工厂类
class PinballFactory {
    private Map<String, Pinball> pinballPool = new HashMap<>();

    public Pinball getPinball(String color, String shape) {
        String key = color + "_" + shape;
        if (!pinballPool.containsKey(key)) {
            // 如果是特殊的颜色和形状,创建非共享的具体享元类
            if (color.equals("Custom") && shape.equals("Special")) {
                pinballPool.put(key, new CustomPinball(color, shape));
            } else {
                pinballPool.put(key, new ConcretePinball(color, shape));
            }
        }
        return pinballPool.get(key);
    }
}

// 游客类
class Visitor {
    private String name;
    private Pinball pinball;
    private CustomPinball specialPinball;

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

    // 选择普通弹球
    public void choosePinball(String color, String shape, int x, int y) {
        PinballFactory factory = new PinballFactory();
        pinball = factory.getPinball(color, shape);
        pinball.display(x, y);
    }

    // 选择特殊弹球
    public void chooseCustomPinball(String color, String shape, int x, int y) {
        PinballFactory factory = new PinballFactory();
        specialPinball = (CustomPinball) factory.getPinball(color, shape);
        specialPinball.display(x, y);
    }

    // 移动普通弹球
    public void movePinball(int deltaX, int deltaY) {
        if (pinball != null) {
            pinball.move(deltaX, deltaY);
        } else {
            System.out.println("Please choose a pinball first!");
        }
    }

    // 开始游戏
    public void playWithPinball() {
        if (pinball != null && specialPinball == null) {
            pinball.play();
        } else if (specialPinball != null) {
            specialPinball.play();
        } else {
            System.out.println("Please choose a pinball first!");
        }
    }
}


public class PinballGame {
    public static void main(String[] args) {
        // 创建游客
        Visitor visitor1 = new Visitor("Alice");
        Visitor visitor2 = new Visitor("Bob");
        Visitor visitor3 = new Visitor("Fidder");

        // 游客选择不同的普通弹球
        visitor1.choosePinball("Red", "Circle", 100, 50);
        visitor2.choosePinball("Blue", "Square", 200, 100);

        // 游客选择不同的特殊弹球
        visitor3.chooseCustomPinball("Custom", "Special", 300, 150);

        // 游客开始游戏
        System.out.println("\n--- Game Start ---");
        visitor1.playWithPinball();
        visitor2.playWithPinball();
        visitor3.playWithPinball();

        // 移动弹球
        System.out.println("\n--- Moving Pinballs ---");
        visitor1.movePinball(10, 20);
        visitor2.movePinball(30, 40);

        // 再次开始游戏
        System.out.println("\n--- Game Restart ---");
        visitor1.playWithPinball();
        visitor2.playWithPinball();
        visitor3.playWithPinball();
    }
}


//public class PinballGame {
//    public static void main(String[] args) {
//        // 创建游客
//        Visitor visitor1 = new Visitor("Alice");
//        Visitor visitor2 = new Visitor("Bob");
//        Visitor visitor3 = new Visitor("Fidder");
//
//        // 游客选择不同的弹球
//        visitor1.choosePinball("Red", "Circle", 100, 50);
//        visitor2.choosePinball("Blue", "Square", 200, 100);
//        visitor3.chooseCustomPinball("Custom", "Special", 300, 150); // 选择非共享的特殊弹球
//
//        // 游客开始游戏
//        visitor1.playWithPinball();
//        visitor2.playWithPinball();
//        visitor3.playWithPinball();
//    }
//}

核心思想

享元模式(Flyweight Pattern)的核心思想是通过共享对象来最大限度地减少内存使用和提高性能。它的主要思想是将对象的状态划分为内部状态(Intrinsic State)和外部状态(Extrinsic State),并尽可能地共享内部状态,而将外部状态留给客户端来管理和传入。

具体来说,享元模式的核心思想包括:

  1. 共享内部状态:享元模式将对象的内部状态(不随环境变化而变化的状态)提取出来并进行共享。这些内部状态被多个享元对象共享,从而减少了系统中重复对象的数量,节省了内存空间。

  2. 区分内部状态和外部状态:享元模式将对象的状态分为内部状态和外部状态两部分。内部状态存储在享元对象内部,外部状态则由客户端在运行时传入享元对象。内部状态决定了享元对象可以共享的部分,而外部状态则决定了享元对象的行为。

  3. 享元工厂管理共享对象:享元工厂负责管理和创建享元对象,并维护一个享元池用于存储已经创建的享元对象。当客户端请求创建一个享元对象时,享元工厂首先查找池中是否存在符合条件的对象,如果存在则返回现有对象,否则创建一个新的对象并加入池中。

  4. 外部状态的传入:客户端在运行时将外部状态传入享元对象,从而影响对象的行为。通过传入不同的外部状态,客户端可以定制享元对象的行为,使得同一个享元对象在不同的环境下具有不同的行为。

通过以上核心思想,享元模式能够有效地降低系统的内存消耗,提高系统的性能和效率,特别是在需要大量相似对象的场景下,能够显著减少重复对象的创建,提升系统的性能表现。

适用场景

享元模式适用于以下情况:

  1. 大量相似对象:当系统中存在大量相似对象时,每个对象的区别在于一些外部状态,而内部状态可以共享时,使用享元模式能够有效地减少内存占用。

  2. 内存或存储资源有限:如果系统的内存或存储资源有限,而需求量又很大,直接创建大量对象会导致资源的极大浪费。通过共享内部状态,可以大幅降低系统的资源消耗。

  3. 外部状态相对较少:如果对象的大部分状态可以被内部化,而且这些外部状态相对较少,只有少量的变化,那么就适合使用享元模式。这样可以减少对象的数量,提高系统的性能。

  4. 对象的状态分为内部状态和外部状态:对象的状态可以划分为内部状态和外部状态,并且内部状态可以共享,外部状态可以相对独立时,适合使用享元模式。这样可以将内部状态与外部状态分离,提高系统的灵活性和可维护性。

享元模式适用于需要创建大量相似对象,并且这些对象之间有一些共享的状态,能够通过外部状态进行区分的情况。通过共享内部状态,可以减少对象的数量,提高系统的性能和资源利用率。

优缺点

亨元模式(Flyweight Pattern)的优点包括:

  1. 减少内存占用:通过共享内部状态,可以减少系统中对象的数量,从而节省内存空间,提高系统的性能和效率。

  2. 提高性能:由于减少了对象的数量,因此可以减少系统的创建和销毁对象的开销,从而提高系统的运行效率。

  3. 支持大规模对象共享:适用于大量相似对象的场景,能够有效地支持大规模的对象共享。

  4. 分离内部状态和外部状态:将对象的状态分为内部状态和外部状态,可以提高系统的灵活性和可维护性,便于管理和扩展。

  5. 符合开放封闭原则:能够在不修改现有代码的情况下,通过引入新的具体享元类来扩展系统的功能,符合开放封闭原则。

亨元模式的缺点包括:

  1. 增加了复杂性:引入了享元工厂和享元池等额外的类和逻辑,使得系统的结构变得更加复杂。

  2. 可能引起线程安全问题:在多线程环境下,需要考虑共享对象的线程安全性,增加了额外的开发和调试成本。

  3. 外部状态的管理:外部状态相对独立,需要客户端负责管理和传递外部状态,增加了客户端的复杂性。

  4. 不适用于所有情况:享元模式适用于有大量相似对象的情况,如果对象差异较大或者大部分状态都是外部状态,就不适合使用享元模式。

备忘录模式【Memento Pattern】

定义

备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不暴露对象实现细节的情况下捕获和恢复其内部状态。它有时也被称为快照模式。这是因为备忘录模式的核心概念是在不破坏封装性的前提下,捕获对象的内部状态,并且可以在需要时将对象恢复到之前的状态,类似于在某个时间点对对象状态进行快照的操作。

备忘录模式中的备忘录对象就像是对象状态的快照,它保存了对象在某个时刻的状态信息。通过备忘录模式,我们可以在不破坏对象封装性的情况下实现状态的保存和恢复,这对于需要支持撤销、重做或者历史记录等功能非常有用。

该模式涉及三个主要角色:Originator(发起者)、Memento(备忘录)和Caretaker(管理者)。

  • Originator(发起者):负责创建一个备忘录,用于记录当前状态,并且可以使用备忘录恢复到之前的状态。发起者可能会修改其状态,并且负责管理其内部状态的变化。

  • Memento(备忘录):用于存储发起者对象的内部状态。备忘录提供了一个接口,可以让发起者将其状态存储到备忘录中或者从备忘录中恢复状态。

  • Caretaker(管理者):负责在适当的时候保存备忘录,并且在需要时将备忘录提供给发起者进行状态恢复。管理者并不知道备忘录的具体内容,它只负责存储和提供备忘录对象。

备忘录模式的关键在于将状态保存在备忘录对象中,以便在需要时恢复对象的状态。这样可以避免在对象内部暴露状态细节,从而提高了对象的封装性和灵活性。备忘录模式通常与命令模式(Command Pattern)等其他模式结合使用,以实现更复杂的功能和交互。 

举例说明

假设你正在玩一个电子游戏,这个游戏是一个冒险类游戏,你的角色可以在不同的关卡中探索世界、战斗怪物并收集宝藏。在这个游戏中,你可能会遇到这样的情况:

当你的角色进入游戏中的第三关时,你发现自己置身于一个神秘的森林深处,四周浓雾笼罩,视野极为有限。在探索过程中,你遭遇了各种怪物和陷阱,但你的勇气和技能让你成功地击败了它们。你收集了许多宝藏和道具,增强了角色的能力。

然而,当你探索到森林的深处时,突然间,一个巨大的黑影出现在你面前。这是游戏中的 Boss,一只强大而凶恶的怪物,它的攻击极具威胁性。你和 Boss 展开了一场激烈的战斗,你奋勇抵抗,但最终你还是被 Boss 击败了。游戏结束画面出现,你的角色倒在了地上,生命力耗尽。

但是,幸运的是,在你进入这个关卡之前,你保存了一个备忘录。这个备忘录记录了你进入第三关时的所有状态:你的生命值、装备情况、拥有的宝藏等等。现在,你可以选择恢复到之前的备忘录状态,重新挑战这个关卡。

你选择使用备忘录,并恢复到了之前的状态。你重新站起来,准备再次面对 Boss 的挑战。这一次,你更加谨慎,更加充分地利用你之前的经验和收集的道具。你打出了更好的战斗表现,最终成功地战胜了 Boss,完成了这一关卡的挑战。

当实现这个情景的代码时,我们需要涉及到几个类:角色(Character)、备忘录(Memento)、备忘录管理者(Caretaker)、Boss,以及游戏控制类(Game)。

首先,我们来定义角色类:

// 角色类
class Character {
    private int health; // 生命值
    private String equipment; // 装备
    private int treasures; // 宝藏数量

    public Character(int health, String equipment, int treasures) {
        this.health = health;
        this.equipment = equipment;
        this.treasures = treasures;
    }

    // 展示角色当前状态
    public void display() {
        System.out.println("Character Status:");
        System.out.println("Health: " + health);
        System.out.println("Equipment: " + equipment);
        System.out.println("Treasures: " + treasures);
    }

    // 获取生命值
    public int getHealth() {
        return health;
    }

    // 获取装备
    public String getEquipment() {
        return equipment;
    }

    // 获取宝藏数量
    public int getTreasures() {
        return treasures;
    }

    // 与 Boss 进行战斗
    public void fightBoss() {
        // 假设 Boss 的攻击力比较高,如果生命值低于一定程度,就会失败
        if (health < 20) {
            System.out.println("You were defeated by the Boss!");
        } else {
            System.out.println("You defeated the Boss and completed the level!");
        }
        // 在战斗中扣除生命值
        this.health -= 10; // 假设 Boss 的攻击力为 10
    }

    // 保存备忘录
    public Memento saveMemento() {
        return new Memento(health, equipment, treasures);
    }

    // 恢复到备忘录状态
    public void restoreFromMemento(Memento memento) {
        this.health = memento.getHealth();
        this.equipment = memento.getEquipment();
        this.treasures = memento.getTreasures();
    }

    // 设置角色状态
    public void setCharacterStatus(int health, String equipment, int treasures) {
        this.health = health;
        this.equipment = equipment;
        this.treasures = treasures;
    }
}

接下来,定义备忘录类:


// 备忘录类
class Memento {
    private int health; // 生命值
    private String equipment; // 装备
    private int treasures; // 宝藏数量

    public Memento(int health, String equipment, int treasures) {
        this.health = health;
        this.equipment = equipment;
        this.treasures = treasures;
    }

    public int getHealth() {
        return health;
    }

    public String getEquipment() {
        return equipment;
    }

    public int getTreasures() {
        return treasures;
    }
}

备忘录保存的是角色进入 Boss 之前的状态。在角色遭遇 Boss 之前,程序会保存角色的当前生命值、装备和宝藏数量等状态信息到备忘录中。这样,在角色与 Boss 战斗之后,如果角色失败了,就可以从备忘录中恢复到之前保存的状态,重新挑战 Boss,而不是回到关卡的开始。 

 然后,定义备忘录管理者类:

// 备忘录管理者类
class Caretaker {
    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}

接着,定义 Boss 类:

// Boss 类
class Boss {
    public void attack(Character character) {
        // 假设 Boss 攻击角色
        System.out.println("Boss is attacking!");
        // 假设 Boss 的攻击力为 10
        character.setCharacterStatus(character.getHealth() - 10, character.getEquipment(), character.getTreasures());
    }
}

最后,定义游戏控制类:

// 游戏控制类
public class Game {
    public static void main(String[] args) {
        // 创建角色
        Character character = new Character(100, "Sword", 5);
        // 创建 Boss
        Boss boss = new Boss();
        // 创建备忘录管理者
        Caretaker caretaker = new Caretaker();

        // 角色开始探索第三关
        System.out.println("--- Exploring the third level ---");
        // 模拟角色遭遇怪物和陷阱,消耗生命值
        character.setCharacterStatus(character.getHealth() - 30, character.getEquipment(), character.getTreasures());

        // 在打 Boss 之前显示一下当前状态
        System.out.println("--- Before fighting Boss ---");
        character.display();

        // 角色进入森林深处,遭遇 Boss
        System.out.println("--- Encountering the Boss ---");
        // 保存备忘录
        caretaker.setMemento(character.saveMemento());
        // Boss 攻击角色
        boss.attack(character);
        // 角色与 Boss 战斗
        character.fightBoss();

        // 在打完 Boss 之后显示一下当前状态
        System.out.println("--- After fighting Boss ---");
        character.display();

        // 角色选择恢复备忘录状态重新挑战
        System.out.println("--- Restoring to previous state ---");
        // 恢复备忘录状态
        character.restoreFromMemento(caretaker.getMemento());
        // 再次与 Boss 战斗
        character.fightBoss();

        // 在打完 Boss 之后再显示一下当前状态
        System.out.println("--- After fighting Boss ---");
        character.display();

        // 模拟角色失败后读档回退
        if (character.getHealth() < 20) {
            System.out.println("--- Restoring to previous state after failure ---");
            // 恢复备忘录状态
            character.restoreFromMemento(caretaker.getMemento());
            // 显示当前状态
            character.display();
        }
    }
}

这样,我们就完成了一个简单的备忘录模式的示例,展示了在游戏中利用备忘录模式记录角色状态并恢复到之前的状态的过程。 

此时我们修改一下初始值为生命值30:

核心思想

备忘录模式的核心思想是在不破坏对象封装性的前提下,捕获对象的内部状态,并且可以在需要时将对象恢复到之前的状态。它主要涉及三个角色:发起者(Originator)备忘录(Memento)管理者(Caretaker)

  1. 原发器(Originator):负责创建备忘录对象,用于存储当前时刻的内部状态,并且可以根据备忘录恢复对象的状态。发起者通常是需要进行状态保存和恢复的对象。

  2. 备忘录(Memento):负责存储发起者的内部状态。备忘录对象可以被创建、存储和恢复。备忘录通常是不可变的,它只能由发起者访问,保证了状态的安全性。

  3. 管理者(Caretaker):负责保存备忘录对象,并且不对备忘录对象的内部状态进行操作。管理者可以存储多个备忘录对象,实现多次撤销或者重做操作。

备忘录模式的关键在于发起者和备忘录之间的解耦,发起者通过备忘录对象保存状态,而不需要暴露内部结构。这样可以保持对象封装性,并且能够方便地实现状态的保存和恢复。

备忘录模式的实现通常涉及以下步骤:

  1. 发起人通过创建备忘录对象来保存自己的内部状态,并且可以通过备忘录对象来恢复状态。
  2. 管理者负责存储备忘录对象,并在需要时将备忘录交还给发起人。
  3. 备忘录对象保存了发起人的内部状态,并且可以通过发起人的请求将状态还原。
  4. 备忘录模式的使用场景通常包括需要支持撤销、重做、历史记录等功能的应用程序中。

总的来说,备忘录模式的核心思想是通过备忘录对象存储对象的内部状态,以实现对象状态的保存和恢复,从而支持撤销、重做等功能,同时保持对象封装性和可维护性。 

适用场景

备忘录模式适用于以下场景:

  1. 需要保存和恢复对象状态的场景:当需要保存对象在某个时间点的状态,并且可以在需要时恢复到该状态时,可以使用备忘录模式。例如,文本编辑器中的撤销和重做功能,游戏中的存档和读档功能等。

  2. 需要实现撤销和重做功能的场景:备忘录模式可以实现对象状态的多次保存和恢复,因此非常适用于需要撤销和重做操作的场景。例如,在编辑器中可以通过备忘录模式实现对编辑操作的撤销和重做。

  3. 需要避免暴露对象内部状态的场景:备忘录模式可以将对象的状态保存在备忘录对象中,而不需要暴露对象的内部结构,从而保持了对象的封装性。

  4. 需要实现历史记录功能的场景:在需要记录对象操作历史的场景中,可以使用备忘录模式。例如,浏览器中的浏览历史记录、应用程序中的操作历史记录等。

备忘录模式适用于需要保存和恢复对象状态、实现撤销和重做操作、保持对象封装性以及记录对象操作历史的场景。

优缺点

备忘录模式(Memento Pattern)具有以下优点和缺点:

优点:

  1. 封装性良好:备忘录模式将备忘录对象与原发器对象解耦,使得原发器对象的状态信息对外部是不可见的,从而提高了系统的封装性。
  2. 简化原发器:备忘录模式可以使得原发器对象的代码更加简洁清晰,不需要在原发器对象中维护大量的历史状态信息。
  3. 支持撤销和重做:备忘录模式可以保存对象的历史状态,因此可以实现撤销和重做功能,使得用户可以方便地回滚到之前的状态。
  4. 良好的扩展性:备忘录模式可以很容易地增加新的备忘录类和相关操作,而不会对原有的系统产生影响。

缺点:

  1. 资源消耗较大:如果需要保存的对象状态信息较大或者频繁进行状态保存和恢复操作,可能会导致系统资源消耗较大。
  2. 备忘录对象管理复杂:如果备忘录对象管理不当,可能会导致备忘录对象过多或者混乱,增加系统的复杂度。
  3. 对性能有影响:频繁进行状态保存和恢复操作可能会影响系统的性能,尤其是在状态信息较大的情况下。

总的来说,备忘录模式提供了一种方便的方式来保存和恢复对象状态,但需要根据具体的应用场景权衡其优缺点,并合理使用。

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值