欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

二十四种设计模式和六种设计原则(4):状态模式、原型模式、中介模式、解释器模式、对偶模式、备忘录模式]的定义、示例、核心思想、场景、优缺点。

最编程 2024-04-01 08:12:25
...

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

目录

状态模式【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. 可能导致性能问题:中介者模式将所有对象之间的通信都集中在一个对象中处理,可能会导致中介者对象成为系统的瓶颈,影响系统的性能。

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

补充说明:

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

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

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

因此,中介者模式适用于多个对象之间紧密耦合、依赖关系错综复杂的情况。一般来说,当类图中出现了蜘蛛网状