user42493
user42493

Reputation: 1103

What to do in the observer design pattern when observers need different parameters to update

Consider a space ship factory that makes space ships. when it produces a ship, it updates the current resources on the planet that it lives and also update the state of the spaceship storage. Hence, the space ship factory is a publisher for two observers. Namely, the resources on the planet and the space ship storage. . When using the observer design pattern, observers have to implement the update method that can be called by the publisher whenever the publisher wants to publish. The publisher stores a list of its observers in an Arraylist and call update by iterating through each observers like the following:

public class SpaceShipFactory implements Publisher{

    private ArrayList<Observer> observers;
    private int mmRequired;
    private int eRequired;
    private int cRequired;
    private int dRequired;
    private int vRequired;

public void notifyObservers() {
    for(Observer obs : observers)
            obs.update(this.mmRequired, this.eRequired, this.cRequired , this.dRequired, this.vRequired );
}
}

The problem is, what we want to update on the planet's resources storage and on the space ship storage is different. Namely, one decreases the number of resources and the other increases the number of spaceships. Hence their update method might have a different signature. So we cannot use the same update method on both observers. Therefore we need to call update one by one as shown in the following pseudocode:

public void notifyObservers() { 
    for(Observer obs : observers)
        if(obs.equals(PlanetResourceStorage)
                obs.update(this.mmRequired, this.eRequired, this.cRequired , this.dRequired, this.vRequired );
        else if (obs.equals(SpaceShipStorage)
            obs.update(numberOfManifacturedLightFight, numberOfManifacturedHeavyFighter, numberOfFacturedStarDestoyer,NumberOfFacturedBattleShips,NumberofFacturedDeathStars)

}
}

I'm wondering if this is how we should do it? Maybe we should create one update method that has arguments all possible values that the publisher wants to publish and the observers can use them all even though they don't need to know all the arguments of the update method. For example:

public class SpaceShipFactory implements Publisher{

    private ArrayList<Observer> observers;
    private int metalRequired;
    private int energyRequired;
    private int carbonRequired;
    private int deuteriumRequired;
    private int vibraumRequired;
    private int numberOfStarDestoyerBuilt;

public void notifyObservers() {
    for(Observer obs : observers)
            obs.update(this.metalRequired, this.energyRequired, this.carbonRequired , this.deuteriumRequired, this.vibriumRequired, this.numberOfStarDestoyerBuilt );
}
}

And hence in the actual implementation of update in planet resource, the argument about the number of ships built is ignored. I also find it very tedious to type out all the arguments for the given update method and I'm wondering if there is an easier way.

For Example the planet resource's implementation of update would be:

public class PlanetResources implements Observer {

    //Amount of resources of each type
    private int metallicMicrolattice;
    private int energy;
    private int carbyne;
    private int deuterium; 
    private int vibranium;

    // The factories to produce the Resources
    private Publisher aMetallicMicrolatticeFactory;
    private Publisher aSolarPlant;
    private Publisher aCarbyneFactory;
    private Publisher aHeavyWaterExtractor;
    private Publisher aVibraniumMine;

    // The Factory to produce mission units
    private Publisher aShipFactory;

    public void update(int mmAmount, int pEAmount, int pCAmount, int pDAmount,
            int pVAmount, int numberOfManifacturedLightFight, int numberOfManifacturedHeavyFighter, int numberOfFacturedStarDestoyer,int numberOfFacturedBattleShips,int numberofFacturedDeathStars) {
        this.metallicMicrolattice = this.metallicMicrolattice + mmAmount;
        this.setCarbyne(this.getCarbyne()+pCAmount);
        this.deuterium = this.deuterium + pDAmount;
        this.energy = this.getEnergy() + pEAmount;
        this.vibranium = this.vibranium + pVAmount;

        // TODO Auto-generated method stub

    }
}

and the spaceship storage's update method would be:

public class StationedShip implements Observer
{
    private ArrayList<SpaceShip> lightFighters;
    private ArrayList<SpaceShip> heavyFighters;
    private ArrayList<SpaceShip> cruisers;
    private ArrayList<SpaceShip> battleShips;
    private ArrayList<SpaceShip> bombers;
    private ArrayList<SpaceShip> battleCuisers;
    private ArrayList<SpaceShip> starDestoyers;
    private ArrayList<SpaceShip> superStarDestoyers;
    private ArrayList<SpaceShip> deathStars;
    private ArrayList<SpaceShip> espoinageProbes;
    private ArrayList<SpaceShip> IonShooters;

    private ArrayList<SpaceShip> smallTransforters;
    private ArrayList<SpaceShip> largeTransporters;
    private ArrayList<SpaceShip> recyclers;
    private ArrayList<SpaceShip> colonialShips;
    @Override
    public void update(int planetMetallicMicrolattice, int planetEnergy, int planetCarbyne, int planetDeuterium,
            int planetVibranium,numberOfManifacturedLightFight, int numberOfManifacturedHeavyFighter, int numberOfFacturedStarDestoyer,int numberOfFacturedBattleShips,int numberofFacturedDeathStars) {
        //code to add ships to the arraylist of ships

    }

}

Upvotes: 1

Views: 346

Answers (3)

Serge Ageyev
Serge Ageyev

Reputation: 419

Publicher should notify observers about what he knowns. Indeed, during development this list of parameters may change, so you may create kind of simple data object for event arguments with everything Publisher knows about the event.

Spaceship Factory may publish event like onShipConstructed with ship type and amount of resources spent. Mining factory may publish event onResourcesMined with update of mining mineral found, like:

public class SpaceShipFactory implements Publisher
{
    private ArrayList<Observer> observers;

    public enum SpaceshipModel
    {
      LightFighter, 
      HeavyFighter, 
      StarDestoyer,
      BattleShip,
      DeathStar
    };

    public static class SpaceShipConstructedArgs
    {
        public SpaceshipModel shipModel;
        public int metalConsumed = 0;
        public int energyConsumed = 0;
        public int carbonConsumed = 0;
        //...
    };

    public void notifyObservers(SpaceShipConstrctedArgs e) {
        for(Observer obs : observers) { obs.update(e); }
    }

    public void someMethod() {
        // ...
        SpaceShipConstructedArgs evt = new SpaceShipConstructedArgs();
        evt.shipModel = SpaceshipModel.LightFighter;
        evt.metalConsumed = 100;
        notifyObservers(evt);
        // ...
    }
}

Then observer for PlanetResources:

public class PlanetResources implements Observer
{
    //Amount of resources of each type
    private int metallicMicrolattice;
    private int energy;
    private int carbyne;
    private int deuterium; 
    private int vibranium;

    //...

    public void update(SpaceShipFactory.SpaceShipConstrctedArgs e)
    {
        // update resources, don't case about ship model
        this.metallicMicrolattice -= e.metalConsumed;
        this.energy -= e.energyConsumed;
        // ...
    }
}    

Then observer for StationedShip:

public class StationedShip implements Observer
{
    //Ships of each type
    private ArrayList<SpaceShip> lightFighters;
    private ArrayList<SpaceShip> heavyFighters;
    //...

    public void update(SpaceShipFactory.SpaceShipConstrctedArgs e)
    {
        // create ships in hangar, don't case about resources consumed
        if (evt.shipModel == SpaceShipFactory.SpaceshipModel.LightFighter)
        {
            lightFighters.add(new SpaceShipLightFighter(...));
        }
        else if (evt.shipModel == SpaceShipFactory.SpaceshipModel.LightFighter)
        {
            heavyFighters.add(new SpaceShipHeavyFighter(...));
        }
        // ...
    }
}    

Note: You may use single map for all hangar units like

public HashMap<SpaceshipModel,ArrayList<SpaceShip>> shipsInHangar;

Even better: You may have you ship types to be subclasses of base class SpaceShip, like

public class SpaceShipLightFighter extends SpaceShip

then you also may have single ArrayList for all ships in hangar (you may differentiate ships using overridden methods like getFirePower() or use instanceof (less preferable) when you need to ensure particular ship type.

public ArrayList<SpaceShip> shipsInHangar;

Upvotes: 1

Rob
Rob

Reputation: 6497

It seems like you might have missed the point of the observer pattern. The idea is supposed to be that when an event occurs, anyone that cares about it can get notified of the event. You got that part. But the pattern is also supposed to insulate the observable from having to know anything other than who to notify, so the observable doesn't need to know what the observers are going to do with the event. That is the part you are missing.

So what does that mean? It means that when a space ship is completed that anyone that cares about this event gets notified. The data that is sent in the notification is simply a handle to the ship that got completed.

Given the ship, the ship storage needs to look up the particulars that it cares about. Maybe the size, mass, etc.

Given the ship, the planetary balance sheet needs to look up what resources went into building that ship (probably by looking up the design and associated bill of materials).

The event (message) that is published doesn't need to include everything that any observer might want to know - it should just include the most specific indicator possible of what happened. That means that you don't say what kind of ship (e.g., "Constitution Class") was produced, you say the specific ship (e.g., "NCC-1701") that was produced. Your database (or data structures) must then allow the observers to take the specific ship (e.g., "NCC-1701") and link that to any information that they need to handle the event.

Upvotes: 3

Torben
Torben

Reputation: 3913

The observable should not be burdened with knowledge of the algorithms implemented by it's observers.

Your update method is too complicated. Just publish a message that describes what happened and let the observers decide which part of the message is relevant to them.

Upvotes: 2

Related Questions