javascriptdesign-patternsinterfacedao

Passing Event specific DAO's vs Generalised DAO's


Is there a right or wrong answer here or is it a pure matter of opinion / depends on the circumstances of our project? It's based around injecting DAO's into other classes which call method's of the DAO within it. I am using javascript to implement this.

Option 1 Should my DAO classes have specific methods which could be used based on an applications state after an event that takes place. Here all methods within the DAO class could be used based on an event which occurs. For example If a specific board square is landed on then a specific DAO classes methods are invoked from the mediation class that the DAO is passed into. This class also receives other DAO's such as a DAO thats responsible for updating a players turn . So not all the functionality exists within the event specific class , just the responsibility pertaining to the targeted entity. This class consists of accessing a specific entity on the database but only consist of methods which will be needed for the event that has taken place. This makes the DAO class super cohesive and I can pass an object consisting of its Event Specific Object DAO as a whole into a mediator class that uses this DAO which provides a good interface if various DAO's have the same methods but different implementations of them.

Option2 Or should I provide more generalised DAO's that I can pass into my mediator classes when I need some of their methods for the required functionality. To prevent passing a whole class in which has multiple redundant methods for the mediator it's passed into ( some methods of this class may not be needed in this case ) then I could pass in static or instance specific methods. But this could soon become wieldy if I need more than a few methods in the class I am passing the DAO methods into and that class also receives other DAO classes within its constructor.

Here is an example

class GeneralisedMediator {

  constructor(type, daoMethod1, daoMethod2, daoObject ){

    this.daoMethod1 = daoMethod1;
    this.daoMethod2 = daoMethod2;
    this.daoObject = daoObject; // a different DAO object part which is part of this mediation object with a single interface
  }

  peformAction(){

    // the two methods and the daoObject passed in collabarate and allow for me to use an interface 
  }
}

class EventSpecificMediator {

  constructor(type, daoObject1, daoObject2){

    this.daoObject1 = daoObject1;
    this.daoObject2 = daoObject2
  }

  peformAction(){

    // the two daoObjects collabrate and daoObject1 has specfic methods needed within this event 
  }
}

Multiple instances of the EventSpecificClass are created inside an array , each instance consists of different types and each instance receives a DAO class which has the same interface as other DAO instance specific classes that may be passed in. The implementation of what occurs depends of the EventSpecificClass thats passed into the mediator class where the mediator class relies on its methods via the interface. All Event Specific Classes have the same interface but have different implementations.

If using option 2, the generalised Mediator also has specific instances stored in an array.

In both examples another class such as a facade like class picks out the type of object needed from the array of either Event Specific Mediator classes or generalised classes from the array using the find method and then the peformAction method is called which mediates between the dependancies.

What is considered best or is it opinion based ? If so then I apologise in advance.


Solution

  • I do not think that it is great idea to extend your classes when new DAO will be added. It seems that we violate Open Closed Principle.

    So, I believe if various DAO should be called, then you need to call a corresponding DAO. It is possible to apply Strategy pattern here. It is also enhances separation of concerns. What I mean is one class has just one goal.

    Let's imagine we have some various DAOs that should be called based on type and let me show a code snippet via TypeScript.

    So code of user input would look like this:

    enum DAOType
    {
        DAO_1, DAO_2
    }
    

    And interface of DAO would look like this:

    export interface DAO {
        retrieve(key: string): string | null;
    }
    

    and concrete implementations would like this:

    export class DAO_1 implements DAO
    {
        public override string retrieve(): string | null { 
            return "I am a DAO_1"
        }
    }
    
    export class DAO_2 implements DAO
    {
        public override string retrieve(): string | null { 
            return "I am a DAO_2"
        }
    }
    

    And then DAOFactory would look like this:

    export class DAOFactory {
        #daosByKey: Record<DAOType, DAO> =  {
                { DAOType.DAO_1, new DAO_1() },
                { DAOType.DAO_2, new DAO_2() }
            };
        }
    
        GetInstanceByDaoType(daoType: DAOType ) {
            return this.#daosByKey[daoType]
        }
    }
    

    And then you can call your DAO where it is necessary like this:

    const daoFactory = new DAOFactory();
    const dao = daoFactory.GetInstanceByDaoType(DAOType.DAO_1);
    

    It can be seen that if you add new DAO, you will add it to DAOFactory class and it complies with Single Responsibility Principle and Open closed principle