二十四种设计模式与六大设计原则(一):【策略模式、代理模式、单例模式、多例模式、工厂方法模式、抽象工厂模式】的定义、举例说明、核心思想、适用场景和优缺点

时间:2024-03-31 09:10:37

目录

策略模式【Strategy Pattern】

定义

举例说明

核心思想 

适用场景

优缺点

代理模式【Proxy Pattern】

定义

举例说明

核心思想

适用场景

优缺点

单例模式【Singleton Pattern】

定义

举例说明

核心思想

适用场景

优缺点

多例模式【Multition Pattern】

定义

举例说明

核心思想

适用场景

优缺点

工厂方法模式【Factory Method Pattern]

定义

举例说明

核心思想

适用场景

优缺点

抽象工厂模式【Abstract Factory Pattern】

定义

举例说明

核心思想

适用场景

优缺点


策略模式【Strategy Pattern】

定义

策略模式(Strategy Pattern)是一种经典的软件设计模式,属于行为型模式的一种。其核心思想是定义一系列算法,将每个算法封装成独立的类,并使它们可以相互替换。这种设计模式使得算法可以独立于使用它的客户端而变化。

在策略模式中,通常有三个关键角色:

  1. 策略接口(Strategy Interface):定义了一个公共接口,用于所有支持的算法。通常由一个接口或抽象类来表示,确保所有具体策略类都实现了相同的方法。

  2. 具体策略(Concrete Strategies):实现了策略接口,提供了具体的算法实现。每个具体策略类都实现了策略接口中定义的方法,以便在上下文中被调用。

  3. 上下文(Context):包含一个成员变量指向策略接口,用于调用其中的算法。上下文允许客户端通过设置不同的策略对象来改变其行为,而无需了解具体算法的实现细节。

具体来说,在策略模式中,上下文对象(Context)持有一个策略接口的引用,并在需要的时候调用具体策略对象(Concrete Strategies)的方法来完成特定的任务。这种设计使得算法的选择和使用与上下文对象解耦,客户端可以根据需要灵活地切换不同的策略,而不必关心具体算法的实现细节。

另外,通过使用策略模式,我们可以将算法的实现封装在各个具体策略类中,这样一来,如果需要新增、修改或删除某个算法,只需添加、修改或删除相应的具体策略类,而不会影响到上下文对象或其他具体策略类。这种高内聚、低耦合的设计使得系统更加灵活和易于维护。

所以,策略模式是一种简单而又强大的设计模式,能够有效地管理和切换算法,提高代码的灵活性和可维护性,是在需要根据不同情况选择不同算法的场景中非常实用的解决方案。

举例说明

假设你是一名游戏玩家,正在玩一款战斗类游戏。在游戏中,你扮演的角色需要选择不同的战斗策略来应对不同的敌人。

游戏中的要素如下:

  1. 角色:你所控制的游戏角色是一名勇敢的骑士。作为骑士,你具有一定的基础能力,比如攻击力、防御力等。你的任务是与游戏中的各种敌人战斗,并取得胜利。

  2. 敌人:游戏中有多种敌人,包括哥布林、巨魔、龙等。每种敌人都有自己独特的特点和能力,比如哥布林速度快但攻击力弱,巨魔力大无穷但移动缓慢,龙拥有强大的火焰攻击等。不同的敌人需要采用不同的战斗策略来对付。

  3. 战斗策略:为了应对不同的敌人,你需要选择合适的战斗策略。战斗策略包括以下几种:

    • 近战攻击:你使用剑或者其他近战武器,直接接近敌人进行攻击。这种战斗策略适用于速度较慢或者防御较弱的敌人。

    • 远程攻击:你使用弓箭或者投掷武器,远距离攻击敌人。这种战斗策略适用于速度较快或者攻击范围广的敌人。

    • 魔法攻击:你释放魔法咒语或者施展魔法技能,对敌人造成魔法伤害。这种战斗策略适用于具有特殊能力或者防御力较高的敌人。

  4. 策略选择器:在战斗过程中,你需要根据当前遇到的敌人情况选择合适的战斗策略。策略选择器负责根据敌人的特点和你的当前状态,自动选择最合适的战斗策略。比如,当你遇到速度较快的哥布林时,策略选择器可能会选择使用远程攻击来保持距离;而当你遇到力大无穷的巨魔时,可能会选择近战攻击来快速解决。

在这个例子中,策略模式的体现如下:

  • 策略接口:定义了一个战斗策略接口,包含了执行战斗的方法。

  • 具体策略:实现了战斗策略接口,包括近战、远程攻击、魔法攻击等不同的具体战斗策略。

  • 角色:持有一个对战斗策略接口的引用,并根据当前遇到的敌人,选择合适的具体战斗策略。

首先,我们定义了一个名为“战斗策略”的接口,其中包含了一个执行战斗的方法,比如 fight()。这个接口规定了所有具体战斗策略类必须实现的方法。

public interface FightStrategy {
    void fight();
}

然后,我们创建了具体的战斗策略类,比如 MeleeStrategy(近战策略)、RangedStrategy(远程攻击策略)、MagicStrategy(魔法攻击策略)等,它们实现了战斗策略接口中的方法,分别表示不同的战斗方式。

// 近战攻击策略
class MeleeStrategy implements FightStrategy {
    @Override
    public void fight() {
        System.out.println("使用剑进行近战攻击!");
    }
}

// 远程攻击策略
class RangedStrategy implements FightStrategy {
    @Override
    public void fight() {
        System.out.println("使用弓箭进行远程攻击!");
    }
}

// 魔法攻击策略
class MagicStrategy implements FightStrategy {
    @Override
    public void fight() {
        System.out.println("施放火球术进行魔法攻击!");
    }
}

游戏中的角色持有一个对战斗策略接口的引用,并在遇到敌人时根据敌人的类型选择合适的战斗策略。比如,当遇到巨魔时,选择近战策略;遇到哥布林时,选择远程攻击策略;遇到龙时,选择魔法攻击策略。

// 角色类
public class Player {
    private FightStrategy fightStrategy;  // 持有一个战斗策略接口的引用

    // 设置战斗策略
    public void setFightStrategy(FightStrategy fightStrategy) {
        this.fightStrategy = fightStrategy;
    }

    // 执行战斗
    public void performFight() {
        if (fightStrategy != null) {
            fightStrategy.fight();
        } else {
            System.out.println("未选择战斗策略!");
        }
    }

    public static void main(String[] args) {
        // 创建角色
        Player player = new Player();

        System.out.println("糟糕!!!遇到巨魔袭击!准备战斗!");
        // 设置战斗策略
        player.setFightStrategy(new MeleeStrategy());
        player.performFight(); // 输出:使用剑进行近战攻击!

        System.out.println("西边冲出一群哥布林!正在快速移动!");
        player.setFightStrategy(new RangedStrategy());
        player.performFight(); // 输出:使用弓箭进行远程攻击!

        System.out.println("我的真神呐!来了一头龙!为了骑士的荣誉,和它拼了!");
        player.setFightStrategy(new MagicStrategy());
        player.performFight(); // 输出:施放火球术进行魔法攻击!
    }
}

通过这种设计,游戏中的角色可以根据不同的敌人选择不同的战斗策略,而不需要在角色类中编写大量的条件语句来判断敌人类型。这样的设计使得系统更加灵活和易于扩展,符合策略模式的核心思想:将算法的定义和使用分离开来,使得算法可以独立于使用它的客户端而变化。

核心思想 

策略模式的核心思想是将算法的定义和使用分离开来,以实现算法的独立演化。这种分离使得算法可以独立于使用它的客户端而变化,而客户端仅需关注如何使用这些算法而不必关心其具体实现细节。这样的设计使得系统更加灵活、可维护,并且方便进行单元测试。

策略模式包含以下关键点(也就是上面我们提到的三个关键角色):

  1. 定义算法接口:首先,我们定义一个算法接口或抽象类,其中声明了算法的方法。这个接口或抽象类通常称为策略接口。

  2. 具体算法实现:针对算法接口,我们可以有多个具体的算法实现类,每个实现类都实现了策略接口中定义的方法,并提供了自己的算法逻辑。

  3. 上下文:上下文是使用算法的类或对象,它持有一个指向策略接口的引用。在需要执行算法的时候,上下文对象调用策略接口中的方法来委托给具体的算法实现类。

通过这种设计,我们实现了算法的独立性,使得算法可以*地变化而不影响到使用它的客户端。当需要新增、修改或删除算法时,我们只需添加、修改或删除相应的具体算法实现类,而不需要修改上下文对象或其他代码。这样的设计使得系统更加灵活,能够适应变化。

另外,由于算法的定义和使用被分离开来,我们可以更方便地进行单元测试。我们可以针对每个具体的算法实现类编写单元测试,验证其逻辑的正确性。同时,在使用算法的客户端中,我们也可以更容易地进行集成测试,保证整体系统的正确性。

综上所述,策略模式通过将算法的定义和使用分离开来,使得系统更加灵活、可维护,并且方便进行单元测试。这种设计思想在软件开发中被广泛应用,特别是在需要根据不同情况选择不同算法的场景中,策略模式能够为我们提供一种简洁而有效的解决方案。

适用场景

策略模式适用于以下场景:

  1. 多种算法选择: 当系统中有多种算法可供选择,并且需要在运行时动态地选择其中一种算法时,策略模式非常适用。例如,排序算法、计算税收算法等场景。

  2. 避免条件语句: 当系统中存在大量的条件语句,根据不同条件执行不同的行为时,可以考虑使用策略模式来避免条件语句的臃肿。策略模式将不同的行为封装到不同的策略类中,使得代码更加清晰和易于维护。

  3. 算法的独立性: 当需要将算法的实现与上下文对象解耦,使得它们可以独立演化时,策略模式是一个很好的选择。每个具体策略类都封装了一个特定的算法,使得算法的变化不会影响到上下文对象或其他具体策略类。

  4. 动态替换算法: 当需要在运行时动态地选择、切换算法时,策略模式非常有用。上下文对象可以持有一个对策略接口的引用,在需要时可以随时替换为另一个具体策略对象,从而改变其行为。

  5. 单一职责原则: 当需要保持每个类的职责单一,使得每个类只负责一种特定的功能或算法时,策略模式可以帮助实现这一目标。每个具体策略类都只负责一个算法的实现,使得代码更加模块化和可维护。

总之,策略模式适用于需要根据不同情况选择不同算法的场景,以及需要将算法的实现与使用者解耦的情况。通过合理地使用策略模式,我们可以使系统更加灵活、可扩展,同时也更易于理解和维护。

优缺点

策略模式的优点和缺点总结如下:

优点:

  1. 提高代码的可维护性和扩展性:策略模式将不同的算法封装到不同的策略类中,使得每个类职责清晰,易于理解和维护。当需要新增、修改或删除算法时,只需操作相应的具体策略类,而不会影响到其他部分的代码。

  2. 降低算法的依赖性:策略模式使得算法与客户端解耦,客户端只需要知道如何使用策略接口,而不需要关心具体的算法实现细节。这样一来,算法的变化不会影响到客户端的代码,提高了系统的灵活性和可维护性。

  3. 简洁清晰的组织方式:策略模式提供了一种简洁、清晰的方式来组织和管理多个算法。每个具体策略类都封装了一个特定的算法,使得代码结构更加清晰易懂。

缺点:

  1. 增加了类和对象的数量:在策略模式中,每个具体策略类都对应一个具体的算法实现,可能会导致类和对象的数量增加。如果系统中有大量的算法需要支持,可能会导致类的数量增加,增加系统的复杂度。

  2. 需要客户端了解各个策略的区别:虽然策略模式将算法的选择和使用解耦,但客户端仍需要了解各个策略的区别,以便在运行时选择合适的策略。如果策略之间的差异不大,可能会增加客户端的理解和维护成本。

虽然策略模式具有诸多优点,但我们在使用时还是需要权衡其优缺点,确保能够在系统设计中选择合适的模式。通常情况下,策略模式适用于需要根据不同情况选择不同算法的场景,以及需要将算法的实现与使用者解耦的情况。

代理模式【Proxy Pattern】

定义

代理模式(Proxy Pattern)是一种结构型设计模式,它允许我们提供一个代理类来控制对其他对象的访问。代理类通常充当客户端与实际目标对象之间的中介,用于控制对目标对象的访问。

在代理模式中,有三个关键角色:

  1. 抽象主题(Subject):定义了代理类和真实主题的共同接口。这个接口可以是一个抽象类或接口,它声明了客户端可以使用的方法。

  2. 真实主题(Real Subject):定义了代理类所代表的真实对象。也就是说,它是客户端最终想要访问的对象。真实主题类实现了抽象主题接口,提供了具体的业务逻辑。

  3. 代理(Proxy):代理持有对真实主题的引用,并提供与真实主题相同的接口,以便于客户端访问。代理类可以在客户端访问真实主题之前或之后执行额外的操作,例如记录日志、控制访问权限等。代理类与真实主题之间的关系通常是关联关系,即代理类中含有一个真实主题对象的引用。

总之,代理模式允许我们通过引入代理类来间接访问目标对象,从而控制访问、增强功能或隐藏复杂性。代理模式的核心在于代理对象充当客户端与实际目标对象之间的中介,管理对目标对象的访问。

举例说明

假设你是一位大学生,正在准备参加一场重要的学术会议。为了备战这场会议,你需要阅读大量的学术论文和专业书籍。然而,由于你每天的课程安排非常紧张,没有足够的时间去书店购买所需的书籍。于是,你决定雇佣一位图书代理人来帮助你购买这些书籍。

在这个场景中,代理模式的角色如下:

  1. 你(委托人):你代表了需要购买书籍的一方,即大学生。由于时间有限,无法亲自前往书店购买书籍。

  2. 图书代理人:代表了你雇佣的帮你购买书籍的人。图书代理人是一位专业的购书员,拥有丰富的购书经验和良好的书店资源。他知道去哪里购买书籍,如何挑选最适合你的书籍,并且能够以更快的速度完成购书任务。

  3. 书店:代表了实际的目标对象,即需要购买的书籍。在这个例子中,书店提供了各种学术论文和专业书籍供你选择。然而,由于你没有时间亲自前去购买,因此需要通过图书代理人来代替你完成购书任务。

图书代理人充当了你和书店之间的中介,负责代表你去书店购买书籍。虽然图书代理人实际上执行购买书籍的任务,但整个过程对你来说是透明的,你只需告诉代理人你需要哪些书籍,然后等待书籍送达即可。这样,代理模式通过引入代理人,实现了你和书店之间的解耦,同时提供了更便捷的购书服务。

抽象主题接口文件,定义了购买书籍的方法 purchaseBooks:

// 抽象主题接口
interface BookPurchase {
    void purchaseBooks(String[] bookTitles);
}

真实主题类文件,实现了 BookPurchase 接口,表示书店类,负责实际购买书籍的操作:

// 真实主题类,即书店
public class BookStore implements BookPurchase {
    @Override
    public void purchaseBooks(String[] bookTitles) {
        System.out.println("购买以下书籍:");
        for (String title : bookTitles) {
            System.out.println("- " + title);
        }
        System.out.println("购书完成!");
    }
}

 代理类文件,同样实现了 BookPurchase 接口,表示图书代理人类,负责代替客户购买书籍:

// 代理类,即图书代理人
public class BookProxy implements BookPurchase {
    private BookStore bookStore; // 代理持有对真实主题的引用

    public BookProxy() {
        this.bookStore = new BookStore(); // 实例化真实主题
    }

    @Override
    public void purchaseBooks(String[] bookTitles) {
        System.out.println("代理人帮您购买书籍:");
        bookStore.purchaseBooks(bookTitles); // 通过真实主题完成购书任务
        System.out.println("书籍购买完成!");
    }
}

上述,被代理类是 BookStore,即真实主题类,而代理类是 BookProxy,即图书代理人。 

客户端类文件,包含了 main 方法,主要用于演示如何使用代理类来购买书籍:

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 实例化图书代理人
        BookPurchase bookProxy = new BookProxy();

        // 客户端委托代理人购买书籍
        String[] booksToPurchase = {"Java编程思想", "设计模式", "计算机网络"};
        bookProxy.purchaseBooks(booksToPurchase);
    }
}

  1. 被代理类(真实主题类):BookStore

    • 被代理类是实际执行任务的类,即真正负责购买书籍的对象。在这个例子中,BookStore 类代表了真实的书店,它实现了 BookPurchase 接口,提供了具体的购买书籍的功能。
    • BookStore 类的方法 purchaseBooks 实际执行了购买书籍的操作,包括打印购买书籍的信息和完成购书的提示。
  2. 代理类:BookProxy

    • 代理类是客户端和被代理类之间的中介,负责控制和管理对被代理类的访问。在这个例子中,BookProxy 类充当了图书代理人的角色。
    • BookProxy 类也实现了 BookPurchase 接口,这样它就能与被代理类拥有相同的方法,使得客户端无需知道具体是哪个类在执行购书操作。
    • 在 BookProxy 类的 purchaseBooks 方法中,它首先打印了代理人帮您购买书籍的提示,然后调用了被代理类 BookStore 的 purchaseBooks 方法,完成了实际的购书任务,最后输出了书籍购买完成的提示。
  3. 客户端代码:Client

    • 客户端代码是使用代理模式的地方,它实例化了代理类 BookProxy 对象,并通过代理类来间接访问真实主题类 BookStore 的功能。
    • 在客户端代码中,创建了代理对象 bookProxy,然后调用代理对象的 purchaseBooks 方法来委托代理人购买书籍。这样,客户端通过代理类来与真实主题类进行交互,无需直接操作真实主题类。

上面的这个例子清晰地展示了代理模式的核心概念:代理类充当了客户端和真实主题类之间的中介,使得客户端可以通过代理类来间接访问真实主题类的功能。代理类能够控制和管理对真实主题类的访问,从而实现了更灵活、更安全、更可控的系统设计。

核心思想

代理模式的核心思想是引入一个代理对象来控制对目标对象的访问,从而实现间接访问目标对象。代理模式允许代理对象充当客户端与实际目标对象之间的中介,可以在客户端访问目标对象之前或之后执行额外的操作。

具体来说,代理模式的核心思想包括以下几个方面:

  1. 控制访问:代理模式允许代理对象控制对目标对象的访问。代理对象可以限制客户端对目标对象的访问,例如通过验证客户端的身份、检查权限等。

  2. 增强功能:代理对象可以在客户端访问目标对象之前或之后执行额外的操作,从而增强目标对象的功能。例如,代理对象可以记录日志、缓存数据、实现懒加载等。

  3. 解耦合:代理模式可以将客户端与目标对象解耦合,客户端无需直接与目标对象交互,而是通过代理对象进行间接访问。这样可以降低系统的耦合度,提高系统的灵活性和可维护性。

  4. 隐藏复杂性:代理模式可以隐藏目标对象的具体实现细节,使客户端无需关心目标对象的内部实现,只需与代理对象进行交互。这样可以降低客户端的复杂性,提高系统的易用性。

代理模式的核心思想是通过引入代理对象来控制对目标对象的访问,并在必要时增强目标对象的功能,从而实现对目标对象的间接访问和管理。

代理模式主要利用了Java的多态特性。在代理模式中,被代理类(真实主题)和代理类实现了相同的接口或继承了相同的父类,这样客户端对于代理类和被代理类就是一种透明的操作,无需关心具体是哪个类在执行。

被代理类实际上是干活的,而代理类则主要是接活、做一些额外的操作或者控制访问。代理类收到客户端的请求后,可以选择性地将任务交给幕后的被代理类去执行。

在代理模式中,通过接口或者继承的方式,确保了代理类和被代理类具有相同的方法签名,这样客户端对于两者的使用方式是一致的。通过这种方式,代理类能够代理任何实现了相同接口的类,而不需要知道被代理类的具体类型,实现了解耦和灵活性。

因此,通过利用Java的多态特性,代理模式能够在保持灵活性的同时,实现对被代理类的控制和增强。

适用场景

代理模式适用于许多不同的场景,特别是在需要控制对对象的访问、增强对象功能或者隐藏对象复杂性时:

  1. 远程代理(Remote Proxy):用于控制对位于不同地址空间的对象的访问。远程代理允许客户端通过代理对象访问远程对象,而不需要了解远程对象的具体实现细节。

  2. 虚拟代理(Virtual Proxy):用于控制对创建开销较大的对象的访问。虚拟代理延迟加载目标对象,直到客户端真正需要使用目标对象时才创建,从而节省系统资源。

  3. 保护代理(Protection Proxy):用于控制对对象的访问权限。保护代理允许代理对象验证客户端的身份、检查权限等,然后决定是否允许客户端访问目标对象。

  4. 缓存代理(Cache Proxy):用于缓存访问过的对象,提高系统性能。缓存代理在客户端访问目标对象时,首先检查缓存中是否已经存在目标对象的副本,如果存在则直接返回缓存的副本,否则再从真实主题获取对象并缓存起来。

  5. 日志记录代理(Logging Proxy):用于记录客户端对目标对象的访问日志。日志记录代理在客户端访问目标对象时,记录访问时间、访问者信息等相关日志信息,以便后续分析和监控。

  6. 动态代理(Dynamic Proxy):用于在运行时动态地创建代理对象。动态代理允许在不修改源代码的情况下为目标对象创建代理,通过反射机制实现代理类的动态生成。

总的来说,代理模式适用于需要控制对对象访问、增强对象功能、隐藏对象复杂性或者提高系统性能的场景。通过合理地使用代理模式,可以提高系统的灵活性、可维护性和可扩展性。

优缺点

代理模式是一种常用的设计模式,它具有许多优点和一些缺点:

优点:

  1. 增强对象功能:代理模式允许代理对象在客户端访问目标对象之前或之后执行额外的操作,从而增强目标对象的功能。这样可以在不改变目标对象的前提下,扩展其功能或行为。

  2. 控制访问:代理模式可以通过代理对象来控制对目标对象的访问。代理对象可以检查客户端的请求,验证客户端的身份、权限等,然后决定是否允许客户端访问目标对象。

  3. 隐藏对象复杂性:代理模式可以隐藏目标对象的具体实现细节,使客户端无需了解目标对象的内部实现。这样可以降低客户端的复杂性,提高系统的可维护性和可扩展性。

  4. 远程访问:代理模式可以实现远程代理,允许客户端通过代理对象访问位于不同地址空间的目标对象。这样可以实现分布式系统中的远程调用,提高系统的灵活性和可扩展性。

  5. 性能优化:代理模式可以实现缓存代理,将访问过的对象缓存起来,避免重复创建对象。这样可以提高系统的性能,减少资源消耗。

缺点:

  1. 增加复杂性:引入代理对象会增加系统的复杂性,因为需要额外的代理类来管理对目标对象的访问。这样会增加代码量和维护成本。

  2. 可能降低性能:在某些情况下,代理模式可能会降低系统的性能。例如,远程代理或虚拟代理可能会引入额外的网络通信或延迟加载,从而影响系统的响应速度。

  3. 过多的代理类:如果系统中存在过多的代理类,会增加系统的复杂性,使代码难以理解和维护。因此,在使用代理模式时需要谨慎设计代理类的数量和结构。

综上所述,代理模式具有许多优点,例如增强对象功能、控制访问、隐藏对象复杂性等,但也存在一些缺点,例如增加复杂性、可能降低性能等。因此,在实际应用中需要根据具体情况权衡利弊,合理使用代理模式来提高系统的灵活性、可维护性和性能。

单例模式【Singleton Pattern】

定义

单例模式(Singleton Pattern)是一种创建型设计模式,它确保类只有一个实例,并提供了一个全局访问点以访问该实例。

在单例模式中,类会通过私有的构造函数来限制该类的实例化,然后提供一个静态方法来允许客户端获取该类的唯一实例。如果该类已经有一个实例存在,那么这个静态方法会返回这个实例;如果没有,它将创建一个新的实例并返回。

单例模式的关键点是:

  1. 私有构造函数:确保其他类无法直接通过构造函数实例化该类的对象,从而避免了多个实例的创建。
  2. 静态方法:提供一个静态方法来获取该类的唯一实例。这个静态方法通常被称为“getInstance()”方法,它负责在需要时创建实例或返回现有实例。
  3. 静态实例变量:保存类的唯一实例,通常是私有的静态变量。
  4. 线程安全:考虑多线程环境下的安全性,确保在多线程环境下也能正确地创建唯一实例。

单例模式经常被用于管理资源、配置信息、日志记录器等一些需要全局访问且只需要一个实例的场景。

举例说明

当我们谈论单例模式时,我们指的是一种设计模式,其目的是确保类只有一个实例,并提供一个全局访问点来访问该实例。这种模式常用于需要在系统中全局访问某个对象的情况下。

让我们以公司中的 HR 经理为例,更详细地解释单例模式。

在一个公司中,HR 经理是负责管理员工入职手续和档案的人员。由于公司可能有成千上万名员工,因此需要一个统一的管理者来处理所有入职事务。这就是 HR 经理的角色。HR 经理不仅负责执行公司的入职流程,还管理着员工的档案信息,确保公司的人力资源管理顺利运作。单例模式确保只有一个 HR 经理的实例存在,并且所有员工的入职手续都由这个实例来处理。

在单例模式中,HR 经理类会有一个私有的静态成员变量来保存唯一的实例,并且会提供一个公共的静态方法来获取这个实例。这个静态方法会检查实例是否已经存在,如果不存在,则创建一个新的实例并返回,如果已经存在,则直接返回已经存在的实例。

这种设计确保了无论何时何地,只要有需要,都可以通过调用 HR 经理类的静态方法来获取 HR 经理的实例。这样就不会出现多个不同的 HR 经理实例存在的情况,保证了系统中只有一个唯一的 HR 经理。

作为单例类,HR 经理的存在确保了入职流程的一致性和数据的一致性。无论员工是在哪个部门,哪个岗位,都会由同一个 HR 经理来处理入职手续,避免了因为多个 HR 经理之间信息不同步导致的混乱和错误。这种单例设计模式确保了公司的人力资源管理高效而准确。

  • HRManager 类只有一个私有的构造函数,这意味着外部不能直接实例化这个类。
  • getInstance() 方法是获取 HRManager 实例的静态方法。在该方法中,首先检查实例是否已经存在,如果不存在则创建一个新实例并返回,如果已经存在则直接返回已经存在的实例。
  • processHiring() 和 manageEmployeeFiles() 方法用于处理员工的入职手续和管理员工的档案信息。这些方法可以在任何地方通过获取 HRManager 实例后调用,确保了入职流程的一致性和数据的一致性。
// HRManager.java - 单例模式的 HR 经理类

public class HRManager {
    // 静态变量 instance 用于保存唯一的实例
    private static HRManager instance;

    // 私有构造函数,防止外部实例化
    private HRManager() {
        // 这里可以放一些初始化逻辑,比如连接数据库等
        System.out.println("HR 经理实例被创建!");
    }

    // 公共静态方法,用于获取 HRManager 实例
    public static HRManager getInstance() {
        // 检查是否已经存在实例,如果不存在则创建一个新实例
        if (instance == null) {
            instance = new HRManager();
        }
        return instance;
    }

    // 入职方法,处理员工入职手续
    public void processHiring(String employeeName) {
        System.out.println("HR 经理正在处理 " + employeeName + " 的入职手续。");
        // 这里可以添加具体的入职逻辑,比如填写表单、发放工作证等
    }

    // 档案管理方法,管理员工档案信息
    public void manageEmployeeFiles(String employeeName) {
        System.out.println("HR 经理正在管理 " + employeeName + " 的档案信息。");
        // 这里可以添加具体的档案管理逻辑,比如归档文件、更新信息等
    }
}

HRMain 类中的 main() 方法用于测试 HRManager 类的单例特性。通过调用 getInstance() 方法获取两个 HRManager 实例,并验证它们是否是同一个实例。然后,通过这两个实例分别调用 processHiring() 和 manageEmployeeFiles() 方法来处理员工的入职手续和管理员工的档案信息。 

// Main.java - 主程序入口

public class HRMain {
    public static void main(String[] args) {
        // 获取 HRManager 实例
        HRManager hrManager1 = HRManager.getInstance();
        HRManager hrManager2 = HRManager.getInstance();

        // 验证是否是同一个实例
        System.out.println("hrManager1 == hrManager2:" + (hrManager1 == hrManager2));

        // 使用 HRManager 处理员工入职手续和档案管理
        hrManager1.processHiring("米勒斯");
        hrManager2.manageEmployeeFiles("埃斯顿");
    }
}

核心思想

单例模式的核心思想是确保一个类只有一个实例,并提供一个全局访问点以访问该实例。这意味着无论在何处创建实例,始终只能获得同一个实例。单例模式的核心思想可以归纳为以下几点:

  1. 单一实例: 单例模式确保一个类只有一个实例存在。无论多少次尝试创建该类的实例,都只会得到同一个实例。

  2. 全局访问点: 单例模式提供一个全局访问点来获取该实例。通常,这是通过一个静态方法实现的,例如 getInstance() 方法。这个方法允许客户端在需要时获取单例实例。

  3. 私有构造函数: 为了防止外部类直接实例化该类的对象,单例类通常会将其构造函数设置为私有的。这样可以确保只有单例类自己能够控制实例的创建过程。

  4. 延迟初始化(可选): 在某些情况下,单例实例可能不需要在应用程序启动时立即创建,而是在第一次访问时才进行初始化。这种延迟初始化可以节省资源,并且在需要时才创建实例。

  5. 线程安全(可选): 在多线程环境中,确保单例模式的线程安全性是很重要的。可以通过加锁、双重检查锁定等技术来确保在多线程环境中正确地创建唯一实例。

总的来说,单例模式的核心思想是通过限制类的实例化并提供全局访问点,确保在整个应用程序中只有一个实例存在,并且能够在需要时方便地访问该实例。

适用场景

单例模式适用于以下场景:

  1. 资源管理器: 当系统中有需要共享的资源,例如数据库连接池、线程池、缓存、配置信息等,可以使用单例模式确保全局只有一个资源管理器实例,避免资源被多次创建和浪费。

  2. 日志记录器: 在应用程序中通常只需要一个日志记录器来记录系统的运行状态和错误信息。单例模式可以确保只有一个日志记录器实例,并且在整个应用程序中都能够方便地访问。

  3. 对话框、窗口管理器: 在图形用户界面(GUI)应用程序中,通常只有一个对话框管理器或窗口管理器来管理系统中的对话框或窗口。使用单例模式可以确保在应用程序中只有一个对话框管理器实例,便于管理和控制窗口的显示和隐藏。

  4. 身份验证服务: 在多用户系统中,通常需要一个全局的身份验证服务来验证用户的身份。单例模式可以确保只有一个身份验证服务实例,提供统一的身份验证接口,便于在整个应用程序中进行身份验证。

  5. 配置信息管理: 当应用程序需要读取配置信息、环境变量或其他全局参数时,可以使用单例模式来管理这些配置信息,确保在整个应用程序中只有一个配置信息管理器实例,便于读取和修改配置信息。

单例模式适用于任何需要全局唯一实例的场景,特别是在需要共享资源、管理状态或提供全局服务的情况下。

优缺点

单例模式具有如下优点和缺点:

优点:

  1. 全局唯一实例: 单例模式确保一个类只有一个实例存在,这意味着在整个应用程序中只有一个全局唯一的实例,可以方便地被访问和使用。

  2. 资源共享: 单例模式可以确保共享资源在整个应用程序中被正确管理和共享,避免资源被多次创建和浪费。

  3. 懒加载: 在某些情况下,单例模式可以实现延迟加载(懒加载),即在第一次访问时才创建实例。这样可以节省系统资源,并且提高了应用程序的启动性能。

  4. 避免竞态条件: 单例模式在多线程环境中可以避免竞态条件(Race Condition),通过线程安全的方式确保只有一个实例被创建。

缺点:

  1. 对扩展性的影响: 单例模式会在全局状态中引入一个全局唯一的实例,这可能会增加代码的耦合性,并且对系统的扩展性产生一定影响。

  2. 隐藏依赖关系: 单例模式会隐藏类的实例化过程,使得其他类对该类的依赖关系不够明显,可能会增加代码的理解和维护难度。

  3. 对单元测试的影响: 单例模式的全局唯一实例可能会影响单元测试的编写和执行,特别是在涉及到单例实例的状态修改时。

  4. 可能引起内存泄漏: 如果单例模式中的实例长时间持有其他对象的引用,并且这些对象也长时间存活,可能会导致内存泄漏的问题。

总之,单例模式在设计时需要慎重考虑,确保它的使用能够符合实际需求,并且能够避免潜在的问题。在某些情况下,单例模式能够带来明显的优势,但在其他情况下可能会引入不必要的复杂性。

多例模式【Multition Pattern】

定义

当我们谈论多例模式时,我们在说的是一种在软件设计中常用的模式,它是单例模式的一种变体。单例模式确保一个类只有一个实例,并提供了一个全局访问点来获取该实例。然而,有时候我们可能需要类的多个实例,但是又不希望无限制地创建实例。这时就可以使用多例模式。

多例模式(Multiton Pattern)是一种设计模式,它允许类有多个实例,但实例数量是有限的,并且每个实例都有一个相关联的键来标识。与单例模式不同,多例模式中的每个实例都有自己的状态和行为。

在多例模式中,类维护一个私有的静态 Map 或者类似的数据结构,用于存储所有可能的实例。这个 Map 中的键通常是用来标识不同实例的唯一标识符,比如字符串、枚举类型等。当需要获取实例时,我们可以通过给定的键来查找对应的实例,如果实例不存在,则创建一个新的实例并存储在 Map 中,然后返回该实例。这样就确保了每个实例都是唯一的,并且可以通过给定的键来获取到相应的实例。

举个例子来说明,假设我们有一个 Logger 类,用于记录日志。在某些情况下,我们可能需要不同类型的日志记录器,比如控制台日志记录器、文件日志记录器和数据库日志记录器。这时,我们可以使用多例模式来创建这些日志记录器的有限数量的实例,并通过给定的键来获取所需类型的日志记录器实例。

总的来说,多例模式允许类拥有多个有限数量的实例,并通过唯一的键来标识和获取这些实例,从而提供了更多的灵活性和控制能力。这种模式适用于需要管理多个实例,但又不希望无限制地创建实例的情况。

举例说明

当我们谈论多例模式时,我们可以想象一个虚构的世界,充满了各种超级英雄,每个都有独特的能力和身份。这个世界面临着不同的威胁和挑战,需要这些超级英雄来保护。

在这个虚构的世界中,我们可以想象一个名为"HeroVerse"的宇宙,充满了各种不同的超级英雄,每个超级英雄都有着独特的身份、背景和超能力。他们的存在是为了保护这个宇宙免受各种威胁和挑战的影响。

在"HeroVerse"中,超级英雄是人们的守护者,他们时刻准备着为普通人和整个世界带来安全和和平。

然而,在某个特定的事件发生后,一个新的超级反派突然涌现出来,他的出现改变了整个宇宙的格局。这位新的反派拥有强大的力量和毁灭性的意图,成为了"HeroVerse"中的最大威胁。超级英雄们必须团结起来,共同对抗这个新的敌人,以维护宇宙的和平与安全。

我们的"Superhero"类就像是"HeroVerse"中超级英雄的抽象表示。每个超级英雄都是这个类的一个实例,而每个实例都有一个独特的名称作为其唯一的标识符。这个名称可能是"Spider-Man"、"Iron Man"等,用来区分不同的超级英雄。

在"HeroVerse"中,普通人们面临着各种各样的威胁和挑战,有时候他们可能会转向超级英雄来寻求帮助和保护。有些人可能会信任所有的超级英雄,而有些人可能更倾向于特定的超级英雄。无论是哪种情况,他们都可以通过获取对应的超级英雄实例来获得所需的支持和保护。

多例模式为"HeroVerse"中的超级英雄提供了一种灵活的管理方式。每个超级英雄实例都是唯一的,并且可以根据需要进行获取和使用。这种模式允许我们在需要管理多个有限实例的情况下,提供更多的灵活性和控制能力,确保"HeroVerse"中的每个超级英雄都能够发挥自己的作用,保护世界的和平与安全。

总之,多例模式允许类具有多个有限数量的实例,并通过唯一的键来标识和获取这些实例。这种模式提供了更多的灵活性和控制能力,适用于需要管理多个有限实例的情况,就像我们所描述的超级英雄的情景一样。

让我们将这个虚构的世界设想成一个名为"HeroVerse"的宇宙。在这个宇宙中,有许多超级英雄,每个都是保护世界的守护者。我们的任务是创建一个程序来管理这些超级英雄,并确保他们能够有效地应对各种威胁。

首先,我们需要一个类来表示超级英雄。我们称之为"Superhero"类。这个类具有属性和方法,用于描述每个超级英雄的特征和行为。例如,它可能有一个名称属性来表示超级英雄的名字,还可能有一个能力属性来描述超级英雄的特殊能力。

public class Superhero {
    private String name;
    private String ability;

    public Superhero(String name, String ability) {
        this.name = name;
        this.ability = ability;
    }

    public String getName() {
        return name;
    }

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

    public String getAbility() {
        return ability;
    }

    public void setAbility(String ability) {
        this.ability = ability;
    }

    public void displayAbility() {
        System.out.println(name + "的特殊能力是:" + ability);
    }

    public void performSpecialAbility() {
        System.out.println(name + "正在执行特殊技能:" + ability);
        // 在这里可以添加具体的特殊技能实现逻辑
    }
}

接下来,我们需要一个管理超级英雄的类。我们称之为"HeroManager"类。这个类负责创建、存储和获取超级英雄的实例。在这个类中,我们使用多例模式来确保每个超级英雄都是唯一的,并且可以根据需要获取对应的实例。

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

public class HeroManager {
    private static final Map<String, Superhero> heroes = new HashMap<>();

    // 静态代码块,用于初始化一些超级英雄实例
    static {
        // 初始化一些超级英雄实例
        heroes.put("Spider-Man", new Superhero("Spider-Man", "超凡蜘蛛能力"));
        heroes.put("Iron Man", new Superhero("Iron Man", "科技战甲"));
        heroes.put("Superman", new Superhero("Superman", "超人力量"));
        heroes.put("Batman", new Superhero("Batman", "黑暗骑士"));
        // 添加更多的超级英雄...
    }

    // 私有构造函数,禁止外部实例化
    private HeroManager() {
    }

    // 静态方法,根据给定的名称获取对应的超级英雄实例
    public static Superhero getHero(String name) {
        return heroes.get(name);
    }
}

在"HeroManager"类中,我们使用一个静态的 Map 来存储所有可能的超级英雄实例。每个实例都有一个唯一的名称作为键,用来标识和区分不同的超级英雄。当需要获取超级英雄实例时,我们可以通过名称来获取对应的实例。

接下来,让我们看看如何在"HeroVerse"中使用这些类来管理超级英雄。

public class HeroVerse {
    public static void main(String[] args) {
        // 获取Spider-Man的实例
        Superhero spiderMan = HeroManager.getHero("Spider-Man");
        System.out.println("欢迎 " + spiderMan.getName() + ",他拥有:" + spiderMan.getAbility());
        spiderMan.displayAbility();
        spiderMan.performSpecialAbility();
        System.out.println();

        // 获取Iron Man的实例
        Superhero ironMan = HeroManager.getHero("Iron Man");
        System.out.println("欢迎 " + ironMan.getName() + ",他拥有:" + ironMan.getAbility());
        ironMan.displayAbility();
        ironMan.performSpecialAbility();
        System.out.println();

        // 获取更多超级英雄的实例
        Superhero superman = HeroManager.getHero("Superman");
        System.out.println("欢迎 " + superman.getName() + ",他拥有:" + superman.getAbility());
        superman.displayAbility();
        superman.performSpecialAbility();
        System.out.println();

        Superhero batman = HeroManager.getHero("Batman");
        System.out.println("欢迎 " + batman.getName() + ",他拥有:" + batman.getAbility());
        batman.displayAbility();
        batman.performSpecialAbility();
    }
}

通过上述代码,我们可以轻松地获取指定名称的超级英雄实例,并且确保每个超级英雄都是唯一的。这种多例模式的设计使得我们可以方便地管理和调用超级英雄,确保他们能够及时、有效地保护"HeroVerse"世界的和平与安全。

核心思想

多例模式(Multiton Pattern)的核心思想是允许一个类有多个实例,但实例数量是有限的,并且每个实例都有一个相关联的键来标识。与单例模式只允许一个类有唯一实例不同,多例模式允许创建有限数量的实例,并通过键来获取所需的实例。

核心思想可以总结为以下几点:

  1. 有限实例:多例模式允许类拥有多个实例,但是这些实例的数量是有限的。这个数量通常是预先确定的,根据应用程序的需求进行设置。

  2. 关联键:每个实例都有一个相关联的键来唯一标识它。这个键可以是字符串、枚举类型或其他类型的标识符。通过这个键,可以获取到对应的实例。

  3. 实例管理:多例模式通常会维护一个数据结构(如 Map)来存储所有可能的实例,以便在需要时快速获取。这个数据结构会将键与实例关联起来,使得可以通过给定的键来检索或创建实例。

  4. 实例的唯一性:尽管多例模式允许多个实例存在,但是每个实例仍然是唯一的。通过键来获取实例时,会根据键的唯一性来确保返回的实例是正确的。

总的来说,多例模式允许一个类拥有多个有限数量的实例,并通过唯一的键来标识和获取这些实例。这种模式提供了更多的灵活性和控制能力,适用于需要管理多个实例,但又不希望无限制地创建实例的情况。

适用场景

多例模式适用于以下场景:

  1. 有限数量的实例:当一个类需要有限数量的实例,而不是无限制地创建实例时,多例模式就很适用。例如,线程池、数据库连接池等资源池类,通常需要限制实例的数量,以便控制资源的分配和利用。

  2. 根据标识符获取实例:当需要根据特定的标识符来获取对应的实例时,多例模式非常有用。这样可以通过唯一的键来管理和获取实例,使得代码更加清晰和易于维护。

  3. 共享资源的管理:在需要共享和管理资源的情况下,多例模式可以确保每个资源实例都被正确地管理和使用。例如,日志记录器、缓存管理器等组件可能需要多个实例来处理不同的日志记录或缓存操作,但是需要确保每个实例都能够正确地管理和记录相关信息。

  4. 对象池:在某些情况下,需要维护一组预先创建的对象,以便在需要时快速获取和使用。多例模式可以用于创建和管理这样的对象池,以提高系统的性能和资源利用率。

多例模式适用于需要有限数量的实例,并且需要根据标识符来获取实例的情况,以及需要管理共享资源和对象池的场景。

优缺点

多例模式的优点:

  1. 控制实例数量:多例模式可以限制实例的数量,避免无限制地创建实例,从而有效地控制系统资源的消耗和管理。

  2. 管理实例:通过唯一的标识符来管理每个实例,使得实例的获取和管理更加清晰和方便。

  3. 提高性能:在某些场景下,预先创建并管理一组对象实例可以提高系统的性能和资源利用率,因为可以避免频繁地创建和销毁对象。

  4. 灵活性:多例模式可以根据需要创建不同类型的实例,并根据唯一标识符来获取对应的实例,使得系统具有更大的灵活性和扩展性。

多例模式的缺点:

  1. 静态实例:多例模式中的实例通常是静态的,一旦创建就会一直存在于内存中,可能会占用较多的内存空间,特别是当实例数量较大时。

  2. 复杂性:多例模式可能会增加系统的复杂性,因为需要维护一个数据结构来存储所有可能的实例,并确保实例的唯一性和正确性。

  3. 难以管理:在一些情况下,需要额外的管理机制来确保实例的正确释放和回收,以避免内存泄漏和资源浪费等问题。

  4. 可能引入全局状态:多例模式中的实例通常是全局可访问的,这可能导致实例之间共享状态,增加系统的耦合性和难以维护性。

综上所述,多例模式在控制实例数量和管理实例方面具有一定的优势,但也存在一些缺点,特别是在静态实例和复杂性方面需要注意。在使用多例模式时,需要根据具体的场景和需求来权衡利弊,并谨慎设计和实现。

工厂方法模式【Factory Method Pattern]

定义

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它提供了一种将对象的实例化延迟到子类中进行的方式。在工厂方法模式中,我们定义一个用于创建对象的接口,但将实际的实例化过程交给子类来实现。 简而言之,工厂方法模式允许我们定义一个创建对象的接口,但具体的对象创建由子类来决定。这样可以将对象的创建与使用相分离,提高了代码的可扩展性和可维护性。

具体来说,工厂方法模式包含以下几个关键角色:

  1. 抽象工厂(Creator):定义了一个创建对象的接口,其中包含一个抽象方法,该方法用于创建产品对象。抽象工厂可以是一个接口或者抽象类。

  2. 具体工厂(Concrete Creator):实现了抽象工厂中定义的创建对象的抽象方法,负责实际创建产品对象。

  3. 抽象产品(Product):定义了产品对象的接口,描述了产品对象的特性和行为。

  4. 具体产品(Concrete Product):实现了抽象产品中定义的接口,具体描述了产品对象的具体实现。

在工厂方法模式中,客户端代码通过与抽象工厂交互来获取所需的产品对象,而具体的产品对象的创建过程则由具体工厂类来完成。这样一来,客户端代码与具体产品的实现细节相分离,使得系统更加灵活,并且更易于扩展和维护。

举例说明

假设我们有一家名为"ChocolateCakeFactory"的蛋糕店,他们使用工厂方法来烘焙不同浓度的巧克力蛋糕。这家蛋糕店意识到市场上有不同口味的顾客,有些人喜欢浓郁的巧克力味道,而另一些人则更喜欢淡淡的巧克力味道。于是,他们决定使用工厂方法来烘焙各种浓度的巧克力蛋糕,以满足不同顾客的口味需求。

在这个蛋糕店里,有一位名为"MasterChef"的主厨,他是这家蛋糕店的创始人,也是巧克力蛋糕的主要烘焙师。MasterChef 拥有一套独特的烘焙工艺和配方,可以根据顾客的口味需求来制作不同浓度的巧克力蛋糕。

首先,MasterChef 开始准备烘焙材料,包括巧克力、面粉、糖等。然后,根据顾客的口味偏好,他调整配方中巧克力的用量,以确定巧克力蛋糕的浓度。

第一次烘焙时,他可能选择增加一些巧克力,制作出一款浓郁的巧克力蛋糕。但不幸的是,配方还没有完全调试好,导致蛋糕的口感略微有些沉重。

第二次烘焙时,MasterChef 决定减少巧克力的用量,使蛋糕口感更轻盈。这次烘焙出的巧克力蛋糕口感确实更轻盈了,但由于减少过度,巧克力味道不够浓郁,有些顾客觉得不够满足。

第三次烘焙时,MasterChef 掌握了烘焙的技巧,成功烘焙出一款口感丰富、巧克力味道浓郁的巧克力蛋糕,完美地符合了顾客的口味需求。

尽管 MasterChef 可以手工调整每一款蛋糕的配方,但他觉得这样太过繁琐。于是,他想出了一个更简单的方法:他准备了不同浓度的巧克力粉,然后根据需要随机选择一种比例来烘焙蛋糕。这样一来,他就可以更轻松地烘焙出各种浓度的巧克力蛋糕,而不必费心费力地调试每一款蛋糕的配方。

在这个故事中,MasterChef 就是工厂方法模式中的工厂,巧克力蛋糕的烘焙过程则是工厂方法。通过工厂方法模式,MasterChef 可以根据需要随机烘焙各种浓度的巧克力蛋糕,而不必关心具体的烘焙过程。这样使得烘焙过程更加灵活、简单,提高了巧克力蛋糕的多样性和生产效率。

我们首先需要定义 ChocolateCake 接口,代表巧克力蛋糕,以及三种具体的巧克力蛋糕类 RichChocolateCake , LightChocolateCake 和 ModerateChocolateCake。

// 巧克力蛋糕接口
interface ChocolateCake {
    void bake(); // 烘焙方法
}
// 浓郁巧克力蛋糕类
class RichChocolateCake implements ChocolateCake {
    @Override
    public void bake() {
        System.out.println("烘焙出一款浓郁的巧克力蛋糕");
    }
}

// 浅淡巧克力蛋糕类
class LightChocolateCake implements ChocolateCake {
    @Override
    public void bake() {
        System.out.println("烘焙出一款浅淡的巧克力蛋糕");
    }
}

// 适中巧克力蛋糕类
class ModerateChocolateCake implements ChocolateCake {
    @Override
    public void bake() {
        System.out.println("烘焙出一款适中的巧克力蛋糕");
    }
}

然后,我们将创建了一个 ChocolateCakeFactory 工厂类,其中有一个 createChocolateCake 方法用于根据不同类型创建对应的巧克力蛋