Archief - [PROG]JAVA Strategy Pattern

Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.

N`Kr1pt

Legacy Member
ik heb hier iets kleins geschreven om wat te spelen met design patterns.
momenteel ben ik bezig met het Strategy Pattern.

In de code staat volgend stukje if-else testen dat volgens mij een perfecte kandidaat is voor strategy pattern:
Code:
if (arg.equals("/EBooks"))
   qry.InsertEbook(aFile);
else if (arg.equals("/MP3"))
   qry.insertMP3(aFile);
else if (arg.equals("/Video"))
   qry.insertVideo(aFile);

de bedoeling is dus een ander soort query uit te voeren afhankelijk van de parameter die meegegeven wordt.

wat houdt het Strategy Pattern in?
By employing the Strategy pattern, you encapsulate the concept that varies, and program to an interface, not an implementation.

Nu heb ik wat zitten denken, maar ik kom er niet helemaal uit.
- The concept that varies lijkt me hier duidelijk de query te zijn (insertMP3Query, insertEBookQuery, insertVideoQuery...)
- program to an interface: we gaan dus een QueryInterface voorzien

ik had dus gedacht aan een QueryInterface, een AbstractQuery class die QueryInterface implementeert, en dan wat subclasses van AbstractQuery (MP3Query, EBookQuery, VideoQuery) die AbstractQuery dus extenden.

Maar hoe elimineer ik die if-else structuur dan, wat uiteindelijk toch de bedoeling is?

wat ik nu heb:
Code:
QueryInterface
---------------

import java.sql.ResultSet;

public interface QueryInterface {
	void insert();
	void remove();
	ResultSet select();
}

Code:
AbstractQuery
---------------

import java.sql.ResultSet;

public class AbstractQuery implements QueryInterface {
	public void insert() {
		
	}
	
	public void remove() {
		
	}
	
	public ResultSet select() {
		return null;
	}
	
}

Code:
MP3Query
---------------

public class MP3Query extends AbstractQuery {
	public void insert() {
		String qryString = "insert into mp3 values('0','1',\"someone\",\"title\",\"size\",\"genre\");";
		
	}
}

forloRn_

Legacy Member
Ik denk niet dat je if-then-else op kunt vangen door het gebruik van het Strategy pattern.

Strategy is in feite een geavanceerde vorm van inheritance. Waar je met inheritance er voor zorgt dat gemeenschappelijk gedrag (d.i. implementatie van methods) van verschillende klassen enkel aanwezig is in één superklasse en overgeërfd wordt door subklassen, encapsuleer je met Strategy dezelfde implementaties van methods in aparte objecten. Wat heeft dat als voordeel? In het geval van inheritance erven àlle subklassen àlle implementaties over, zelfs als die niet van toepassing zijn. In het geval van Strategy bepaal je zelf welke klassen welke implementaties krijgen; die implementaties kunnen zelfs leeg zijn. Bijkomend voordeel: je kunt het gedrag van die klassen at runtime aanpassen.

Volgens mij kom je hier enkel weg met het Factory pattern.

By the way, waarom een QueryInterface én een (lege) AbstractQuery?

[BAT] Hydra

Legacy Member
Ik denk dat je misschien een verkeerd beeld van het strategy pattern hebt. Ik zal proberen om eens uit te leggen wat een strategy precies is.

Quote van "Design Patterns, Element of Reusable Object-Oriented Software":

"Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm very independently from clients that use it."

Om even de link te leggen met wat jij zegt

"By employing the Strategy pattern, you encapsulate the concept that varies, and program to an interface, not an implementation."

geef ik een voorbeeld van een strategy:

De laatste keer dat ik een strategy heb gebruikt was wanneer ik een yahtzee-AI maakte. Ik neem aan dat je weet wat yahtzee is anders ga je van de rest van mijn tekst niet al te veel begrijpen. Op bepaalde momenten moet de computerspeler beslissen welke dobbelstenen hij opnieuw gooit en welke hij laat liggen. De computerspeler kan dit beslissen aan de hand van zijn scorekaartje en de gegooide dobbelstenen. De computer kan dom denken en random wat dobbelstenen kiezen of de computer kan slim denken en zien van welke soort er het meeste is gegooid, en de andere soorten opnieuw gooien. De computer zal zijn keuze overlaten aan een klasse die verantwoordelijk is voor het maken van deze keuzes. Zo'n klasse zal de Strategie interface implementeren:

interface Strategie
...
// selecteer de dobbelstenen die opnieuw gegooid moeten worden op basis van de score kaart en de gegooide dobbelstenen
public Vector<Dobbelsteen> selecteerDobbelstenen(ScoreKaart scoreKaart,Vector<Dobbelsteen> gegooideDobbelstenen)

Om de keuze van het selecteren van dobbelstenen te maken heb ik 2 strategy klassen gebouwd. De ene DommeStrategie en andere SlimmeStrategie. Ze implementeren beiden de interface Strategie, maar omdat hun implementatie van de selecteerDobbelstenen methode anders is geven ze andere resultaten (geselecteerde dobbelstenen) terug.

Overal in mijn code waar ik "selecteerDobbelstenen" aanroep zal ik dit aanroepen op een object waarvan ik enkel weet dat het de Strategie interface implementeert.

Een lijntje code is genoeg om het gedrag van mijn AI te veranderen:
Strategie mijnStrategie = new DommeStrategie();

N`Kr1pt

Legacy Member
hmz ok, verhelderend.

hoe zouden jullie mijn code dan refactoren?
het moet toch eleganter kunnen dan dit niet?

Code:
if (arg.equals("/EBooks"))
   qry.InsertEbook(aFile);
else if (arg.equals("/MP3"))
   qry.insertMP3(aFile);
else if (arg.equals("/Video"))
   qry.insertVideo(aFile);

het is duidelijk dat er vanalle queries uitgevoerd moeten worden, ze hebben dus allemaal gemeen dat er interactie is met een database.
daarom had ik gedacht aan een DataAccessor Interface/Abstract Class, DataAccessor, die alles operaties bepaald die er kunnen gebeuren op een database.
zo ontstaat er een standaard manier om de database te manipuleren en als ik/iemand anders later een nieuwe class wil maken (om applicaties in een DB bij te houden bv) dan kan deze interface/abstract class geimplementeerd/extended worden.

Code:
public abstract Class/Interface DataAccessor {
   void insert();
   void delete();
   void select();
   void update();
   ....
}

dan wat concrete classes die de DataAccessor implementeren/extenden: MP3Data, EBookData, VideoData, ...
Code:
public class MP3Query implements/extends DataAccessor {
   hier implementeren we alle methods (desnoods leeg, indien niet gebruikt)
}

en dan in de if-else structuur:
Code:
DataAccessor da;
if (arg.equals("/EBooks"))
   da= new EBookData();
else if (arg.equals("/MP3"))
   da= new MP3Data();
else if (arg.equals("/Video"))
   da= new VideoData();

of gebruik je hier nu best een Abstarct Class ipv een Interface voor?
Ik denk dat ik hier best voor een abstract class opteer, aangezien niet alle concrete classes alle methods moeten implementeren?

WHiSPy

Legacy Member
Goeie raadgeving qua coding style trouwens: als je 'n constante waarde hebt, zorg dan dat die links van de equals staat. Waarom? Zo kan je geen nullpointerexception krijgen op die method call. Het bespaart je daarenboven ook 'n extra check. ;)

Ice

Legacy Member
Wat ge ook kunt doen is
Code:
public class QueryFactory {
private static final QueryFactory instance;
private Map queryMap;
 private QueryFactory() {
    queryMap = new HashMap();
    queryMap.put("Ebooks", new EbookQuery());
    queryMap.put("Mp3", new Mp3Query());
    queryMap.put("Videos", new VideoQuery());
 }

public static QueryFactory getInstance() {
   if (instance == null) {
      instance = new QueryFactory();
   }
   return instance;
}

 public QueryInterface getQuery(String code) {
   return queryMap.get(code);
 }
}
Code:
public interface QueryInterface {
  public void insert();
  public void delete();
  public void update();
}
EbookQuery, Mp3Query, VideoQuery zijn dan implementors van de QueryInterface.

jodeman

Legacy Member
wat Ice schrijft is een mooie oplossing maar wat je schreef met die if/else is evengoed strategy.

http://www.fluffycat.com/Java-Design-Patterns/Abstract-Factory/
http://www.javacamp.org/designPattern/
http://www.patterndepot.com/put/8/JavaPatterns.htm

hier vind ik altijd raad. Wat ik altijd miste aan patterns was, je las altijd wel wat ze deden, maar nergens stond uitleg van wanneer je met dit probleem zit, kan dit patroon je voort helpen. Een van die site's heeft die uitleg wel. Weet niet meer welke.

forloRn_

Legacy Member
Ice zei:
Wat ge ook kunt doen is
Code:
public class QueryFactory {
private static final QueryFactory instance;
private Map queryMap;
 private QueryFactory() {
    queryMap = new HashMap();
    queryMap.put("Ebooks", new EbookQuery());
    queryMap.put("Mp3", new Mp3Query());
    queryMap.put("Videos", new VideoQuery());
 }

public static QueryFactory getInstance() {
   if (instance == null) {
      instance = new QueryFactory();
   }
   return instance;
}

 public QueryInterface getQuery(String code) {
   return queryMap.get(code);
 }
}
Code:
public interface QueryInterface {
  public void insert();
  public void delete();
  public void update();
}
EbookQuery, Mp3Query, VideoQuery zijn dan implementors van de QueryInterface.

Dat kan ik bezwaarlijk een factory noemen: je returnt altijd dezelfde objecten.

Iets anders: Is het trouwens niet de bedoeling dat je parameters kan meegeven met insert(), remove() en select()?

Ice

Legacy Member
forloRn_ zei:
Dat kan ik bezwaarlijk een factory noemen: je returnt altijd dezelfde objecten.

Iets anders: Is het trouwens niet de bedoeling dat je parameters kan meegeven met insert(), remove() en select()?
errrr kijk eens goed, ik return een ander implementatie van eenzelfde interface. Dus de operaties zijn hetzelfde, de implementatie kan verschillen.

Die insert/remove/select dingen zijn maar voorbeelden die ik heb overgenomen uit deze thread, het gaat hem hier tenslotte om het principe ;)

N`Kr1pt

Legacy Member
forloRn_ zei:
Dat kan ik bezwaarlijk een factory noemen: je returnt altijd dezelfde objecten.

Iets anders: Is het trouwens niet de bedoeling dat je parameters kan meegeven met insert(), remove() en select()?

dat lijkt me wel degelijk een goede factory method hoor?
ik had ondertussen ongeveer dezelfde factory method uitgewerkt, enkel werkte ik met if-else ipv een hashmap.
die hashmap zal wss wel sneller zijn (niet dat dat zoveel uitmaakt voor zo weinig elementen)?

uiteraard worden er ook parameters meegegeven aan de insert, remove etc., maar ik wou hier gewoon even een simpele voorstelling geven, en die parameters deden eigenlijk toch weinig terzake bij het voorgestelde probleem, vandaar.

merci voor de handige tips alvast!
ik ga wanneer ik tijd heb wat refactoren en ik ga de structuur hier dan eens opneuw posten voor een nieuwe review.

forloRn_

Legacy Member
Ice zei:
errrr kijk eens goed, ik return een ander implementatie van eenzelfde interface. Dus de operaties zijn hetzelfde, de implementatie kan verschillen.

Kijk zelf eens goed. Je maakt maar op één plaats nieuwe queries aan: in de constructor van je factory. Calls naar getQuery() leveren je altijd de zelfde drie objecten op.

Ice

Legacy Member
forloRn_ zei:
Kijk zelf eens goed. Je maakt maar op één plaats nieuwe queries aan: in de constructor van je factory. Calls naar getQuery() leveren je altijd de zelfde drie objecten op.

Yep maar dat kan evengoed de bedoeling zijn toch?

insert / delete / update en dergelijke gaan toch hetzelfde zijn voor 1 soort object.

killgore

Legacy Member
De enige echte vervanging voor die if/else is (zoals aangegeven) idd een (hash)map.

Factory pattern op zich heeft niets te maken met die if/else vervangen.

WHiSPy

Legacy Member
Ice zei:
Wat ge ook kunt doen is
Code:
public class QueryFactory {
private static final QueryFactory instance;
private Map queryMap;
 private QueryFactory() {
    queryMap = new HashMap();
    queryMap.put("Ebooks", new EbookQuery());
    queryMap.put("Mp3", new Mp3Query());
    queryMap.put("Videos", new VideoQuery());
 }

public static QueryFactory getInstance() {
   if (instance == null) {
      instance = new QueryFactory();
   }
   return instance;
}

 public QueryInterface getQuery(String code) {
   return queryMap.get(code);
 }
}
Code:
public interface QueryInterface {
  public void insert();
  public void delete();
  public void update();
}
EbookQuery, Mp3Query, VideoQuery zijn dan implementors van de QueryInterface.

Da's geen factory, maar eerder 'n implementatie in de richting van het typesafe enum design pattern.
Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.
Terug
Bovenaan