4

I have the following class structure:

public abstract class Creature{
   private String name;
   //strategy pattern composition
   private SkillInterface skill;
}

public interface SkillInterface {
   void attack();
}

public class NoSkill implements SkillInterface {
   @Override
   public void attack() {
       //statements
   }
}

My goal is to persist Creature objects at one table in database. Subclasses of SkillInterface are without any fields. As they determine the behaviour, I want to convert selected SkillInterface class name to a String, as I only need to persist the classname of the current skill strategy of creature, with a String like skill.getClass().getSimpleName(). I tried to implement it with @Converter annotation, using AttributeConverter class to convert SkillInterface to String and save, but always had mapping exceptions. I want to be able to save it as String and retrieve as SkillInterface object.

But how can I implement it with Hibernate? Or do I have a design mistake?

w32-mydoom
  • 531
  • 5
  • 11
  • Can you also post your table structure and how the data should look like ? And what do you mean by retrieve interface by reference? Are you planning to retrieve whole object ? – Amith Kumar Jul 03 '18 at 02:13
  • Hi Amith, I edited my question and gave more details. What I want to do is to be able to save Creature object at one table in database. I want to convert SkillInterface to String, as I only need to persist the classname of the current skill strategy of creature. Can provide more details if needed. – w32-mydoom Jul 03 '18 at 07:13
  • If you only need to persist the name of the current skill strategy, why not use Enum to achieve something like that? – kaba713 Jul 03 '18 at 08:42
  • Just wanted to see if I could implement and persist a Strategy Pattern using interfaces and it's subclasses only. Enum may be a good idea, but wouldn't it be a monolithic Enum to add all method declarations and to lose flexibility of an Interface? – w32-mydoom Jul 03 '18 at 08:48
  • OK. SO if I understand you correctly the thing you are probably looking for Inheritance combined with a DiscriminatorValue. Take a look at: https://stackoverflow.com/a/48415922/6505091 – kaba713 Jul 03 '18 at 10:09
  • @kaba713 I tried your advise, but started having MappingException's aswell. This is probably because I am using an interface over a class or abstract class. I have found a solution for now (posted answer below) with the Converter annotation and an AttributeConverter, which is a really flexible and useful solution I think. – w32-mydoom Jul 03 '18 at 13:48

2 Answers2

6

Ok looks like I have found a basic solution that can be used to persist Strategy Pattern interfaces implementations. I used a @Converter annotation and a AttributeConverter class to convert strategy class names to column while saving to database and cast the retrieved String back to strategy class as following:

@Entity
public class Creature {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Convert(converter = SkillConverter.class)
    private SkillInterface skill;
}

public class SkillConverter implements AttributeConverter<SkillInterface,String> {
    @Override
    public String convertToDatabaseColumn(SkillInterface skill) {
        return skill.getClass().getSimpleName().toLowerCase();
    }

    @Override
    public SkillInterface convertToEntityAttribute(String dbData) {
        //works as a factory
        if (dbData.equals("noskill")) {
            return new NoSkill();
        } else if (dbData.equals("axe")) {
            return new Axe();
        }
        return null;
    }
}

public interface SkillInterface {
    public String getSkill();

    void attack();
}


public class NoSkill implements SkillInterface{
    public String getSkill() {
        return getClass().getSimpleName();
    }

    @Override
    public void attack() {
        //strategy statements
    }
}
w32-mydoom
  • 531
  • 5
  • 11
0

You can use a proxy field to this for you like below:

abstract class Creature {
    @Column
    private String name;
    // strategy pattern composition
    private SkillInterface skill;

    @Column
    private String skillName;

    public String getSkillName() {
        return skill.getClass().getSimpleName();
    }

    public void setSkillName(String skillName) {
        //ignore
    }
}
Amith Kumar
  • 4,400
  • 1
  • 21
  • 28
  • I thought about this way, but you will need an additional Factory Pattern to handle conversion of skillName string to an SkillInterface subclass everytime you retrieve an object. I think there should be a more convenient solution for this within Hibernate, such as @Convert annotation. It is actually a perfect place to use it but I couldn't achieve to build it somehow because of MappingExceptions or other problems. – w32-mydoom Jul 03 '18 at 08:20