Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using strategy design pattern with an abstract parameter

I am currently working on a pretty simple project to improve my SOLID and Design Patterns Knowledge. The idea was to create a "Smart Lock" for a door that can recognize a person by different parameters such as fingerprints, facial recognition, etc.

I immediately saw the potential in using the Strategy Design Pattern, and therefore I created a Lock interface and a Key abstract class:

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

public abstract class Key {
    private String id;

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

I created two classes that will extend Key - FacePhoto and FingerPrint:

public class FacePhoto extends Key {
}

public class FingerPrint extends Key {
}

Then I created classes that implement Lock such as FingerPrintRecognizer and FacialRecognizer:

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");
        }
    }
}

I couldn't really find a better way to handle cases in which users of the Lock interface will try to open Locks with keys that don't fit. Also, I had trouble with the "instanceof" if statement because it appears in every class that implements Lock.

Is Strategy a good practice in this case? if not, what would be a fine alternative (a different Design Pattern perhaps).

like image 727
JacobKreynin Avatar asked Aug 21 '26 07:08

JacobKreynin


2 Answers

Strategy pattern provides the ability to change behavior at runtime. In your case a particular concrete implementation of Lock can work with specific implementation of key and thereby the logic does not allow the behavior change so the pattern is a misfit in current implementation.

Example for Strategy pattern.

 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();
}

In your case you need to refactor the abstractions to better fit the logic.

As a refactoring (not using strategy pattern for current situation as forcing design pattern usuage is a bad practice, currently L from SOLID principles is being violated ) you can consider another answer to your question. https://stackoverflow.com/a/49763677/504133

like image 113
nits.kk Avatar answered Aug 23 '26 21:08

nits.kk


A Lock can be opened using a specific type of Key

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

interface Key {
    // TODO
}

A Door is composed of multiple Lock objects. Each Lock may require a different type of key. But you want to keep the interface "single-entry".

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

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

We need some way to dispatch the key to the correct lock. If a FacePhoto is used for the Key, we want the faceLock to be used.

Currently, the Key is the only one who knows/decides which lock should be used. Why not allow the Key to decide which lock to use?

First, for the key to decide which lock to use, we need to somehow pass these locks to the key. We can hide the different locks behind a facade and pass that to 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);
    }
}

Now to implement your keys:

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
    }
}

You can't use the wrong key with the wrong lock. All the potential locks are specified via LockSet. Since LockSet exposes a type-safe interface, you cannot try to open a Lock<FacePhoto> with a FingerPrint, the compiler won't let you (which is a good thing - catch mismatch errors before runtime). You cannot try to use unsupported keys either.

This design is called the visitor pattern. If there's something you disagree with, or need further explanation, please let me know.

like image 40
Vince Avatar answered Aug 23 '26 20:08

Vince