再话设计模式-单态模式

1 说明

      本文主要讲解单态设计模式(Monostate Pattern),单例设计模式是比较常见的,在我之前的文章2.3章节中详细阐述过它的多种不同实现(https://blog.csdn.net/weixin_37624828/article/details/105929809)。但是,单例模式也有一些缺点是需要考虑的。本文将对单例设计模式再次做出新的评价,进而引出一种新的设计模式供读者在设计时提供一种全新的思路。

2 单例模式-再评价

好处:

  • 跨平台:适用合适的中间件(例如,RMI),可以把单例模式扩展为多个JVM和多个计算机工作。
  • 适用于任何类:只需把一个类的构造函数变为私有的,并且在其中增加相应的静态函数和变量,就可以把这个类变为单例。
  • 可以通过派生创建:给定一个类,可以创建它的一个单例子类。
  • 延迟求值:如果单例从未使用过,那么就决不会创建它。

代价:

  • 销毁方法未定义:没有好的方法去销毁(destory)一个单例,或者解除其职责。即使添加一个decommission方法把instance的值置为null,系统种的其他模块仍然持有对该单例实例的引用。这样,随后对getInstance方法的调用会创建另外一个实例,致使同时存在两个实例。这个问题在C++种尤为严重,因为实例可以被摧毁,可能会导致去提领一个已被正式停止使用(decommission)的对象。
  • 不能继承:从单例类派生出来的类并不是单例。如果要使其成为单例,必须要增加所需的静态方法和变量。
  • 效率问题:每次调用getInstance方法都会执行if语句。就大多数调用而言,if语句是多余的。
  • 不透明性:单例的使用者知道它们正在使用一个单例,因为它们必须要调用getInstance方法。

      在程序设计中,可以考虑使用单态模式,在单态模式中设置和获取一个值可以通过不同的对象来实现,看起来两个实例就好像是具有不同名字的同一个对象一样。
      单态模式是通过将单例模式中保存的变量定义为静态变量,从而达到多个对象的状态保持一致。

3 单态模式

      考虑一个例子,地铁十字转门简单的有限状态机。十字转门开始时处于Locked状态。如果投入一枚硬币,它就迁移到Unlocked状态,开启转门,复位可能出现的任何告警状态,并把硬币放到收集箱柜中。如果此时乘客通过了转门,转门就迁移回Locked状态并且把门锁上。其状态转移图如下所示:
地铁十字转门有限状态机代码清单:

  1. Turnstile.java
  2. TestTurnstile.java — 测试用例

1 Turnstile.java

public class Turnstile {
    private static boolean isLocked = true;
    private static boolean isAlarming = false;
    private static int itsCoins = 0;
    private static int itsRefunds = 0;
    protected final static Turnstile LOCKED = new Locked();
    protected final static Turnstile UNLOCKED = new Unlocked();
    protected static Turnstile itsState = LOCKED;

    public void reset(){
        lock(true);
        alarm(false);
        itsCoins = 0;
        itsRefunds = 0;
        itsState = LOCKED;
    }

    public boolean locked(){
        return isLocked;
    }

    public boolean alarm(){
        return isAlarming;
    }

    public void coin(){
        itsState.coin();
    }

    public void pass(){
        itsState.pass();
    }

    protected void lock(boolean shouldLock){
        isLocked = shouldLock;
    }

    protected  void alarm(boolean shouldAlarm){
        isAlarming = shouldAlarm;
    }

    public int coins(){
        return itsCoins;
    }

    public int refunds(){
        return itsRefunds;
    }

    public void deposit(){
        itsCoins++;
    }

    public void refund(){
        itsRefunds++;
    }
}

class Locked extends Turnstile {
    @Override
    public void coin() {
        itsState = UNLOCKED;
        lock(false);
        alarm(false);
        deposit();
    }

    @Override
    public void pass(){
        alarm(true);
    }
}

class Unlocked extends Turnstile {
    @Override
    public void coin(){
        refund();
    }

    @Override
    public void pass(){
        lock(true);
        itsState = LOCKED;
    }
}

2 TestTurnstile.java

public class TestTurnstile extends TestCase {
    public TestTurnstile(String name) {
        super(name);
    }

    public void setUp() {
        Turnstile t = new Turnstile();
        t.reset();
    }

    public void testInit() {
        Turnstile t = new Turnstile();
        assert (t.locked());
        assert (!t.alarm());
    }

    public void testCoin() {
        Turnstile t = new Turnstile();
        t.coin();
        Turnstile t1 = new Turnstile();
        assert (!t1.locked());
        assert (!t1.alarm());
        assertEquals(1, t1.coins());
    }

    public void testCoinAndPass() {
        Turnstile t = new Turnstile();
        t.coin();
        t.pass();

        Turnstile t1 = new Turnstile();
        assert (t1.locked());
        assert (!t1.alarm());
        assertEquals("coins", 1, t1.coins());
    }

    public void testTwoCoins() {
        Turnstile t = new Turnstile();
        t.coin();
        t.coin();

        Turnstile t1 = new Turnstile();
//        assertEquals("unlocked", !t1.locked());
        assert (!t1.locked());
        assertEquals("coins", 1, t1.coins());
        assertEquals("refunds", 1, t1.refunds());
        assert (!t1.alarm());
    }

    public void testPass() {
        Turnstile t = new Turnstile();
        t.pass();
        Turnstile t1 = new Turnstile();
        assert (t1.alarm());
        assert (t1.locked());
    }

    public void testCancelAlarm() {
        Turnstile t = new Turnstile();
        t.pass();
        t.coin();
        Turnstile t1 = new Turnstile();
        assert (!t1.alarm());
        assert (!t1.locked());
        assertEquals("coin", 1, t1.coins());
        assertEquals("refund", 0, t1.refunds());
    }

    public void testTwoOperations() {
        Turnstile t = new Turnstile();
        t.coin();
        t.pass();
        t.coin();
        assert (!t.locked());
        assertEquals("coins", 2, t.coins());
        t.pass();
        assert (t.locked());
    }
}

4 总结

      使用单态模式可以在透明性、可派生性、多态性上面获得好处,但是要在不可转换、效率、内存、平台方面付出代价。
好处:

  • 透明性:使用单态对象和使用常规的对象没有什么区别,使用者不需要知道对象是单态的。
  • 可派生性:单态的派生类都是单态的。事实上,单态的所有派生类都是同一个单态的一部分,它们共享相同的静态变量。
  • 多态性:由于单态的方法不是静态的,所以可以在派生类中重写(override)它们。因此,不同的派生类可以基于同样的静态变量表现出不同的行为。

代价:

  • 不可转换性:不能透过派生把常规类转换成单态类。
  • 效率问题:因为单态是真正的对象,所以会导致许多的创建和摧毁开销。
  • 内存占用:即使从未使用单态,它的变量也要占据内存空间。
  • 平台局限性:单态不能跨多个JVM或者多个平台工作。

      从实现的角度来看,单例模式一般使用私有构造函数、一个静态变量和一个静态方法对实例化进行控制和限制。而单态模式只是简单地把对象的所有变量变成静态的。
      如果希望通过派生去约束一个现存类,并且不介意它的所有调用者都必须调用getInstance方法来获取访问权,那么单例是最合适的。如果希望类的单一性本质对使用者透明,或者希望使用单一对象的多态派生对象,那么单态是最合适的。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值