Home > Net >  Question on diamond operator for design pattern strategy
Question on diamond operator for design pattern strategy

Time:08-28

Small question regarding the diamond operator and design pattern strategy for Java, please.

I would like to implement a very specific requirement:

  • there are some objects to store (in my example called MyThingToStore)

  • and the requirement is to store them with different kinds of data structures, for comparison.

Therefore, I went to try with a strategy pattern, where each of the strategies is a different way to store, I think this pattern is quite lovely.

The code is as follows:


public class MyThingToStore {

    private final String name;

    public MyThingToStore(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyThingToStore that = (MyThingToStore) o;
        return Objects.equals(name, that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }

    @Override
    public String toString() {
        return "MyThingToStore{"  
                "name='"   name   '\''  
                '}';
    }

}

public class MyStorage {

    private final StorageStrategy storageStrategy;

    public MyStorage(StorageStrategy storageStrategy) {
        this.storageStrategy = storageStrategy;
    }

    public void addToStore(MyThingToStore myThingToStore) {
        storageStrategy.addToStore(myThingToStore);
    }

    public int getSize() {
        return storageStrategy.getSize();
    }

}

public interface StorageStrategy {

    void addToStore(MyThingToStore myThingToStore);

    int getSize();

}


public class StorageUsingArrayListStrategy implements StorageStrategy {

    private final List<MyThingToStore> storeUsingArrayList = new ArrayList<>();

    @Override
    public void addToStore(MyThingToStore myThingToStore) {
        storeUsingArrayList.add(myThingToStore);
    }

    @Override
    public int getSize() {
        return storeUsingArrayList.size();
    }

}


public class StorageUsingHashSetStrategy implements StorageStrategy{

    private final Set<MyThingToStore> storeUsingHashSet = new HashSet<>();

    @Override
    public void addToStore(MyThingToStore myThingToStore) {
        storeUsingHashSet.add(myThingToStore);
    }

    @Override
    public int getSize() {
        return storeUsingHashSet.size();
    }

}


public class Main {

    public static void main(String[] args) {
        final StorageStrategy storageStrategy = new StorageUsingArrayListStrategy();
        final MyStorage myStorage = new MyStorage(storageStrategy);
        myStorage.addToStore(new MyThingToStore("firstItem"));
        myStorage.addToStore(new MyThingToStore("duplicatedSecondItem"));
        myStorage.addToStore(new MyThingToStore("duplicatedSecondItem"));
        System.out.println(myStorage.getSize()); //changing strategy will return a different size, working!
    }
}

And this is working fine, very happy, especially tackled the requirement "easy to change the data structure to do the actual store".

(By the way, side question, if there is an even better way to do this, please let me know!)

Now, looking online at different implementations of strategy patterns, I see this diamond operator which I am having a hard time understanding:


MyThingToStore stays the same.


public class MyStorage {

    private final StorageStrategy<MyThingToStore> storageStrategy; //note the diamond here

    public MyStorage(StorageStrategy<MyThingToStore> storageStrategy) {
        this.storageStrategy = storageStrategy;
    }

    public void addToStore(MyThingToStore myThingToStore) {
        storageStrategy.addToStore(myThingToStore);
    }

    public int getSize() {
        return storageStrategy.getSize();
    }

    @Override
    public String toString() {
        return "MyStorage{"  
                "storageStrategy="   storageStrategy  
                '}';
    }

}


public interface StorageStrategy<MyThingToStore> {
    //note the diamond, and it will be colored differently in IDEs
    void addToStore(MyThingToStore myThingToStore);

    int getSize();

}

public class StorageUsingArrayListStrategy implements StorageStrategy<MyThingToStore> {

    private final List<MyThingToStore> storeUsingArrayList = new ArrayList<>();

    @Override
    public void addToStore(MyThingToStore myThingToStore) {
        storeUsingArrayList.add(myThingToStore);
    }

    @Override
    public int getSize() {
        return storeUsingArrayList.size();
    }

}


public class StorageUsingHashSetStrategy implements StorageStrategy<MyThingToStore> {

    private final Set<MyThingToStore> storeUsingHashSet = new HashSet<>();

    @Override
    public void addToStore(MyThingToStore myThingToStore) {
        storeUsingHashSet.add(myThingToStore);
    }

    @Override
    public int getSize() {
        return storeUsingHashSet.size();
    }

}

public class Main {

    public static void main(String[] args) {
        final StorageStrategy<MyThingToStore> storageStrategy = new StorageUsingArrayListStrategy();
        final MyStorage myStorage = new MyStorage(storageStrategy);
        myStorage.addToStore(new MyThingToStore("firstItem"));
        myStorage.addToStore(new MyThingToStore("duplicatedSecondItem"));
        myStorage.addToStore(new MyThingToStore("duplicatedSecondItem"));
        System.out.println(myStorage.getSize()); //changing strategy will return a different size, working!
    }
}


And both versions will yield the same good result, also be able to answer requirements.

My question is: what are the differences between the version without a diamond operator, and the version with the diamond operator, please?

Which of the two ways are "better" and why?

While this question might appear to be "too vague", I believe there is a reason for a better choice.

CodePudding user response:

I think the confusion comes from how you named type parameter for StorageStrategy in your 2nd example.

Let's name it T for type instead. T in this case is just a placeholder to express what type of objects your StorageStrategy can work with.

public interface StorageStrategy<T> {
    void addToStore(T myThingToStore);
    int getSize();
}

E.g.

StorageStrategy<MyThingToStore> strategy1 = // Initialization 
StorageStrategy<String> strategy2 = // Initialization 
strategy1.addToStore(new MyThingToStore("Apple"));
// This works fine, because strategy2 accepts "String" instead of "MyThingToStore"
strategy2.addToStore("Apple");
// Last line doesn't work, because strategy1 can only handle objects of type "MyThingToStore"
strategy1.addToStore("Apple");

To make it work properly, you need to change your different StorageStrategy implementations to also include the type parameter.

public class StorageUsingHashSetStrategy<T> implements StorageStrategy<T> {

    private final Set<T> storeUsingHashSet = new HashSet<>();
    @Override
    public void addToStore(T myThingToStore) {
        storeUsingHashSet.add(myThingToStore);
    }

    @Override
    public int getSize() {
        return storeUsingHashSet.size();
    }

}

And lastly you also want to have a type paremeter for MyStorage

public class MyStorage<T> {

    private final StorageStrategy<T> storageStrategy;

    public MyStorage(StorageStrategy<T> storageStrategy) {
        this.storageStrategy = storageStrategy;
    }

    public void addToStore(T myThingToStore) {
        storageStrategy.addToStore(myThingToStore);
    }

    public int getSize() {
        return storageStrategy.getSize();
    }

}

Now you can create a MyStorage and can use it to store essentially any object into it and not just MyThingToStore. Whether that is something you want or not is up to you.

CodePudding user response:

In the second code sample in the declaration of the interface StorageStrategy<MyThingToStore>, MyThingToStore is a Type Variable.

I.e. it's not the actual type, only a placeholder for a type, like T. The common convention is to use single-letter generic type variables (T, U, R, etc.), otherwise it might look confusing like in this case.

Note that in the class declarations, like:

public class StorageUsingArrayListStrategy 
    implements StorageStrategy<MyThingToStore>

MyThingToStore is no longer a type variable, but the name of the class MyThingToStore because in this case parameterized interface is implemented by a non-parameterized class (i.e. the actual type known to the compile is expected to be provided).

  • Related