使用带有抽象参数的策略设计模式

Using strategy design pattern with an abstract parameter

我目前正在做一个非常简单的项目来提高我的 SOLID 和设计模式知识。 这个想法是为门创建一个 "Smart Lock",可以通过指纹、面部识别等不同参数识别人。

我立即看到了使用策略设计模式的潜力,因此我创建了一个 Lock 接口和一个 Key 抽象 class:

public interface Lock {
    boolean unlock(Key key);
}

public abstract class Key {
    private String id;

    public String getId(){
        return (this.id);
    }
}

我创建了两个 class 将扩展 Key - FacePhotoFingerPrint:

public class FacePhoto extends Key {
}

public class FingerPrint extends Key {
}

然后我创建了 class 实现 Lock 的实体,例如 FingerPrintRecognizerFacialRecognizer:

public class FacialRecognizer implements Lock {
    @Override
    public boolean unlock(Key key) throws Exception {
        if(key instanceof FacePhoto){
            //check validity
            return true;
        }
        else {
            throw new Exception("This key does not fit this lock");
        }
    }
}

public class FingerPrintRecognizer implements Lock {
    @Override
    public boolean unlock(Key key) throws Exception {
        if(key instanceof FingerPrint){
            //check validity
            return true;
        }
        else {
            throw new Exception("This key does not fit this lock");
        }
    }
}

我真的找不到更好的方法来处理 Lock 界面的用户将尝试使用不适合的钥匙打开锁的情况。 此外,我在使用 "instanceof" if 语句时遇到了麻烦,因为它出现在每个实现 Lock.

的 class 中

在这种情况下,策略是一种好的做法吗?如果没有,什么是更好的选择(也许是不同的设计模式)。

策略模式提供了在运行时改变行为的能力。在您的情况下,Lock 的特定具体实现可以与 key 的特定实现一起使用,因此逻辑不允许行为更改,因此该模式不适合当前实现。

策略模式示例。

 class A{
    private Behavior b; //behavior which is free to change
    public void modifyBehavior(Behavior b){
         this.b = b;
    }
    public  void behave(){
          b.behave(); // there is no constraint of a specific implementation but any implementation of Behavior is allowed.
     }
 }

 class BX implements Behavior {
     public void behave(){
           //BX behavior
     }
 }

 class BY implements Behavior {
     public void behave(){
           //BY behavior
     }
 }

interface Behavior {
      void behave();
}

在您的情况下,您需要重构抽象以更好地适应逻辑。

作为重构(不针对当前情况使用策略模式,因为强制使用设计模式是一种不好的做法,目前 L 来自 SOLID违反了原则)你可以考虑对你的问题的另一个答案。

可以使用特定类型的 Key

打开 Lock
interface Lock<K extends Key> {
    void unlockUsing(K key);
}

interface Key {
    // TODO
}

一个Door由多个Lock对象组成。每个 Lock 可能需要不同类型的密钥。但是你想保留界面"single-entry"。

class Door {
    private Lock<FacePhoto> faceLock;
    private Lock<FingerPrint> printLock;

    public void unlockUsing(Key key) {
        // which lock to use?
    }
}

我们需要一些方法来将钥匙分配给正确的锁。 如果 FacePhoto 用于 Key,我们需要 faceLock 待用。

目前,Key 是唯一一个 knows/decides 应该使用哪个锁。 为什么不允许 Key 决定使用哪个锁?

首先,为了让钥匙决定使用哪把锁,我们需要以某种方式将这些锁传递给钥匙。我们可以隐藏门面后面的不同锁并将其传递给 Key:

class Door {
    private LockSet locks;

    public void unlockUsing(Key key) {
        key.unlock(locks); // the key will decide!
    }
}

interface Key {
    void unlock(LockSet locks);
}

class LockSet {
    private Lock<FacePhoto> faceLock;
    private Lock<FingerPrint> printLock;

    public void unlockUsing(FacePhoto photo) {
        faceLock.unlockUsing(photo);
    }

    public void unlockUsing(FingerPrint print) {
        printLock.unlockUsing(print);
    }
}

现在实施您的密钥:

class FacePhoto implements Key {
    public void unlock(LockSet locks) {
        locks.unlockUsing(this);
    }

    public boolean matches(FacePhoto photo) {
        boolean matches = false;
        // TODO: check if match
        return matches;
    }
}

class FingerPrint implements Key {
    public void unlock(LockSet locks) {
        locks.unlockUsing(this);
    }

    public boolean matches(FingerPrint print) {
        // TODO: check if match
    }
}

不能用错钥匙配错锁。所有可能的锁都是通过LockSet指定的。由于 LockSet 公开了一个 type-safe 接口,你不能尝试用 FingerPrint 打开一个 Lock<FacePhoto>,编译器不会让你(这是一件好事 - 捕获不匹配运行前错误)。您也不能尝试使用不受支持的密钥。

这个设计叫做visitor pattern。如果您不同意某些内容,或需要进一步解释,请告诉我。

一般来说,当两个抽象之间的关系是one-to-many时,策略模式是好的。例如,如果您有一把锁和许多可以用来打开锁的钥匙。例如,以下是策略模式的一个很好的例子:

public class Lock {

     public void unlock(Key key) {
         // Unlock lock if possible
     }
}

public interface Key {
    public int someState();
}

public class FooKey implements Key {

    @Override
    public int someState() { ... }
}

public class BarKey implements Key {

    @Override
    public int someState() { ... }
}

你的问题是一个many-to-many问题,很多锁可以用多个钥匙打开,其中一些钥匙可以打开一些锁而不能打开其他锁。对于这类问题,Visitor Pattern 是一个不错的选择,其中算法是解锁过程,对象是锁。这种方法的好处是,成功或失败锁(特定密钥是否解锁特定锁)包含在简单的方法中,而无需使用 instanceof

通常,使用 instanceof 表示需要某种形式的多态性(即不是测试每个提供的对象以查看它是否是某种类型并基于该类型执行逻辑,该类型应该具有一种多态方法,其行为因对象类型而异)。这个问题很常见,有一个标准的重构来代替它:Replace Conditional with Polymorphism.

要为您的目的实现访问者模式,您可以尝试类似于以下的操作:

public class UnlockFailedException extends Exception {

    public UnlockFailedException(Lock lock, Key key) {
        this("Key " + key.getClass().getSimpleName() + " failed to unlock lock " + lock.getClass().getSimpleName());
    }

    public UnlockFailedException(String message) {
        super(message);
    }
}

public interface  Lock {
    public void unlock(Key key);
}

public interface Key {
    public void unlock(FacialRecognizer lock) throws UnlockFailedException;
    public void unlock(FingerPrintRecognizer lock) throws UnlockFailedException;
}

public class FacialRecognizer implements Lock {

    @Override
    public void unlock(Key key) {
        key.unlock(this);
    }
}

public class FingerPrintRecognizer implements Lock {

    @Override
    public void unlock(Key key) {
        key.unlock(this);
    }
}

public class FacePhoto extends Key {

    @Override
    public void unlock(FacialRecognizer lock) throws UnlockFailedException {
        // Unlock the lock
    }

    @Override
    public void unlock(FingerPrintRecognizer lock) throws UnlockFailedException {
        throw new UnlockFailedException(lock, this);
    }
}

public class FingerPrint extends Key {

    @Override
    public void unlock(FacialRecognizer lock) throws UnlockFailedException {
        throw new UnlockFailedException(lock, this);
    }

    @Override
    public void unlock(FingerPrintRecognizer lock) throws UnlockFailedException {
        // Unlock the lock
    }
}

将每个 Lockunlock 逻辑分组到一个抽象的 class 中可能很诱人(因为它对每个 Lock 实现都是相同的),但这会打破这种模式。通过将 this 传递给所提供的 Key,编译器知道要调用哪个重载方法。这个过程称为double-dispatch。虽然看起来很繁琐,但调用的逻辑很简单(一行),因此虽然有重复,但并不严重。

这种方法的缺点是 Key 接口必须为 Lock 的每个 实现 提供一个 unlock 方法。如果缺少一个,编译器会在 Lock 实现时报错,因为它的 unlock 方法会在 Key 上调用 unlock,它不包含接受的方法新的 Lock 实现。从这个意义上说,编译器充当检查以确保 Key 实现可以处理(解锁或解锁失败)每个 Lock 实现。

您还可以实现一个 KeyRing 包含许多 Key 个对象可以使用每个 Key 对象解锁一个 Lock 直到找到一个打开 Lock。如果KeyRing上没有Key可以打开Lock,一个UnlockFailedException:

public class KeyRing {

    public final List<Key> keys = new ArrayList<>();

    public void addKey(Key key) {
        keys.add(key);
    }

    public void removeKey(Key key) {
        keys.remove(key);
    }

    public void unlock(Lock lock) throws UnlockFailedException {

        for (Key key: keys) {
            boolean unlockSucceeded = unlockWithKey(lock, key);
            if (unlockSucceeded) return;
        }

        throw new UnlockFailedException("Could not open lock " + lock.getClass().getSimpleName() + " with key ring");
    }

    private boolean unlockWithKey(Lock lock, Key key) {
        try {
            lock.unlock(key);
            return true;
        }
        catch (UnlockFailedException e) {
            return false;
        }
    }
}

如果UnlockFailedException太突兀,Keyunlock方法可以改成return一个boolean表示是否解锁成功了。例如:

public interface Key {
    public boolean unlock(FacialRecognizer lock);
    public boolean unlock(FingerPrintRecognizer lock);
}

public class FacePhoto extends Key {

    @Override
    public boolean unlock(FacialRecognizer lock) {
        // Unlock the lock
        return true;
    }

    @Override
    public boolean unlock(FingerPrintRecognizer lock) {
        return false;
    }
}

public class FingerPrint extends Key {

    @Override
    public void unlock(FacialRecognizer lock) {
        return false;
    }

    @Override
    public void unlock(FingerPrintRecognizer lock) {
        // Unlock the lock
        return true;
    }
}

使用 boolean return 值也简化了 KeyRing:

的实现
public class KeyRing {

    public final List<Key> keys = new ArrayList<>();

    public void addKey(Key key) {
        keys.add(key);
    }

    public void removeKey(Key key) {
        keys.remove(key);
    }

    public boolean unlock(Lock lock) throws UnlockFailedException {

        for (Key key: keys) {
            boolean unlockSucceeded = lock.unlock(key);
            if (unlockSucceeded) return true;
        }

        return false;
    }
}