ugrás a tartalomhoz

Interfacek es konstansok

janoszen · 2013. Okt. 16. (Sze), 14.24
Sziasztok!

Kicsi OOP-s fejtores kovetkezik. Adott a kovetkezo minta kod:
abstract class AbstractService {
  public function getName() {
    return static::SERVICE_NAME;
  }
}

interface iDatabaseService {
  const SERVICE_NAME='database';
}

class DatabaseService extends AbstractService implements iDatabaseService {

}
A cel itt az, hogy barki implementalhasson egy masik Database szolgaltatast sok erolkodes nelkul. A jelenlegi konstrukcioban viszont semmi trivialisan nem mutatja azt egy implementalonak, hogy egy masik (uj) szolgaltatasnal kellene biztositani egy SERVICE_NAME konstanst.

Ki hogy oldana ezt meg?
 
1

Kivétel

Poetro · 2013. Okt. 16. (Sze), 14.45
Én inkább kivételt dobnék a getName metódusban, ami erre utal. Ez eléggé egyértelművé tenné, hogy mit is kellene implementálni.
2

Miert kell a konstans? Az

duplabe · 2013. Okt. 16. (Sze), 15.01
Miert kell a konstans? Az interfaceben legyen benne a getName, az abstractbol maradjon ki, igy minden servicenek implementalnia kell.
3

Konstans

janoszen · 2013. Okt. 16. (Sze), 15.04
A konstans azert kell, mert mashol ezzel akarok hivatkozni az adatbazis service-re. Az abstract getName nekem is a legjobb megoldasnak tunik jelenleg, de ha lehet, szeretnem meg egyszerubbe tenni.
6

"Enum"?

BlaZe · 2013. Okt. 16. (Sze), 23.11
Lehet picit mellémegy, de valami ilyen enum-szerű dolog?

interface iDatabaseService {
}

class PostgreSQL implements iDatabaseService {
}

class MySQL implements iDatabaseService {
}

class DB {
	public static function POSTGRESQL() {
		return new PostgreSQL();
	}
	
	public static function MYSQL() {
		return new MySQL();
	}
}

var_dump(DB::POSTGRESQL());
Vagy kicsit messzebbre gurult gyógyszerekkel... :) Azt nem állítom, hogy szép megoldás, de...

interface iDatabaseService {
	public function getName();
}

abstract class AbstractDatabaseService implements iDatabaseService {
	public function __construct(){
		DB::registerDB($this);		
	}
}

class PostgreSQL extends AbstractDatabaseService {		
	public function getName() {
		return 'POSTGRESQL';
	}
}

class MySQL extends AbstractDatabaseService {
	public function getName() {
		return 'MYSQL';
	}
}

class DB {
	private static $dbs = array();
	
	public static function registerDB(iDatabaseService $db){
		self::$dbs[$db->getName()] = $db;
	}
	
	public static function __callStatic($name,$arguments){
		return self::$dbs[$name];
	}	
}

new PostgreSQL();
new MySQL();

var_dump(DB::POSTGRESQL());
var_dump(DB::MYSQL());
object(PostgreSQL)[1]
object(MySQL)[2]
Itt annyi kikötés mondjuk van, hogy az abstract ősből kell származni, és az ős konstruktorát meg kell hívni.
4

Mindez arra kéne, hogy egyedi

szjanihu · 2013. Okt. 16. (Sze), 18.26
Mindez arra kéne, hogy egyedi azonosító alapján ki tudd szedni a megfelelő implementációt a DIC-ből? Vagy legalábbis valami ilyesmi van a dolog mögött? Ha kifejtenéd, mit szeretnél elérni, könnyebb dolgunk lenne.

Egyébként az interfészbe ha felveszed a
public static function getName();
függvényt, az nem oldja meg a problémádat? Ezesetben a konstanssal nem kellene foglalkozni.
5

Igen

janoszen · 2013. Okt. 16. (Sze), 18.33
Igen, valami ilyesmi az elkepzeles. Azt szeretnem, ha lenne egy iDatabaseService interface, ami definialja az adatbazis eleresenek mondjat, illetve tartalmazza azt a kulcsot, amivel hozza lehet szolni. Egesz pontosan a kulcs barhol lehet, de egy adott service osztalyrol meg kellene tudni allapitani, hogy o milyen kulcsra hallgat.
7

Szerintem a statikus függvény

szjanihu · 2013. Okt. 17. (Cs), 07.38
Szerintem a statikus függvény az interfészben megoldja a problémádat. Ha enumszerű dolgot is bevinnél a játékba, ajánlom az én megoldásomat.
8

Nem

janoszen · 2013. Okt. 17. (Cs), 10.31
Sajnos nem oldja meg, mert így minden 'database service' osztálynak implementálnia kell és ugyanazt a stringet kell visszaadniuk (database). Helyette én azt szeretném, hogy a string az egész rendszerben egyetlen egyszer szerepeljen. Lehet egyebkent trait lesz belole.
9

Szerintem rossz helyen van az

inf · 2013. Okt. 17. (Cs), 11.12
Szerintem rossz helyen van az a name konstans, és kívülről kellene beinjektálnod példányosításkor a DIC-el az egyedi azonosítót, ha mindenképp azt szeretnéd, hogy tudja magáról a service, hogy mi az azonosítója. Az ilyesmi szvsz nem tartozik a service feladatkörébe, inkább a DIC-be kellene tenni egy katalógust ezzel kapcsolatban... A példányosítás, a példányok számának a nyomonkövetése, singleton, stb... egyértelműen az IoC container feladata, és nem az osztályé. Pontosan azért használjuk, hogy az ilyesmiket kiemeljük az osztályból.
10

Akkor nem értem. Azt hittem

szjanihu · 2013. Okt. 17. (Cs), 19.02
Akkor nem értem. Azt hittem minden implementáció valami egyedi azonosítót fog visszaadni. Ha csak egy kell, akkor használd az interfészben lévő konstanst.
12

Pelda

janoszen · 2013. Okt. 18. (P), 01.04
Itt van a jelenlegi implementacio. Ha van idod egy picit turni benne, nezd meg a service layert.
14

Service/ServiceRegistry.php

inf · 2013. Okt. 18. (P), 13.21
Service/ServiceRegistry.php

Itt kéne átírni szerintem, hogy névvel lehessen regisztrálni service-t, és ne maga a service tartalmazza a saját nevét. Esetleg a registry injektáljon neki uniqueId-t, ha a név nem fontos. De ez az én véleményem... Érdemes lenne megnézni, hogy mások hogy csinálják.
16

Nem fontos

janoszen · 2013. Okt. 18. (P), 13.30
A nev nem fontos, a lenyeg az, hogy a tobbi modul tudjon ra hivatkozni valahogy.
18

Tudsz mutatni egy példát erre

inf · 2013. Okt. 18. (P), 13.33
Tudsz mutatni egy példát erre a hivatkozásra?

Nézegettem a config fájlt, nekem úgy néz ki, hogy semmi akadálya, hogy onnan adj nevet az egyes service-eknek... Nem teljesen tiszta, hogy ezekkel kapcsolatban mi a specifikáció, egy service osztály csak egyszer kerülhet példányosításra, vagy csak egy adatbázis típusú service lehet a rendszerben, stb... Írnál erről valamit?
19

pl

janoszen · 2013. Okt. 18. (P), 16.58
Nem vagyok hép előtt, de nézz rá a tPluginService-re.
20

Oks, este megnézem.

inf · 2013. Okt. 18. (P), 17.30
Oks, este megnézem.
23

Nem akarom lerántani az

inf · 2013. Okt. 19. (Szo), 00.22
Nem akarom lerántani az egészet, hogy átrágjam magam a kódon, meg hogy mit hol használnak benne. Ha jól sejtem arról van szó, hogyha egy Service-t használni akarsz, akkor a ServiceRegistry-től kéred el a neve alapján.

Szerintem teljesen jó megoldás erre a problémára, ha a config-ban megadod a Service nevét mondjuk egy id attribútummal, ami ugye egyedi, vagy a dtd-ben megadod, hogy unique legyen az attribútum, amit erre használsz. (Nem vagyok benne biztos, hogy ezt dtd-vel lehet, keveset foglalkoztam dtd-vel, valamivel többet xsd-vel, a helyedben inkább ez utóbbit használnám, ha van rá lehetőség, mert sokkal könnyebb vele az élet).

Ha bármilyen megkötésed van a Service típusokkal kapcsolatban - pl: egy bizonyos interface-t csak egy regisztrált Service implementálhat - akkor azt a ServiceRegistry átírásával meg tudod csinálni. Ha interface alapján akarnál lekérni egy bizonyos Service-t, az ugyanígy megoldható. Mindkettő zűrös karbantarthatóság szempontjából, mert az azonosító stringeket kézzel kell átírni, ha bármi változik. Tudtommal nincs erre igazán jó megoldás. Az autocomplete-t úgy lehet mégis bekapcsolva tartani, ha a Service elkérése után átadod egy setternek a Service-t, amin be van állítva, hogy annak milyen interface-t kell implementálnia. Nyilván emiatt a Service elkérője is egy IoC container lesz, mert ő is példányosít valamit, amin egy setter-t hív meg. Tehát elvileg a Service neveket csak alsóbb absztrakciós szintű - egy-egy modulhoz tartozó - IoC containerek tudhatják. Nem akarom továbbgondolni ezt a kelleténél, én sem hiszem, hogy tudom a tökéletes rendszer titkát, csak próbálkozom egy ideje - több-kevesebb sikerrel - valami olyat csinálni, ami megfelel a SOLID elveknek...
11

Nem világos, mi a célod

tgr · 2013. Okt. 17. (Cs), 23.14
Nem világos, mi a célod tulajdonképpen. Az, hogy ha valaki definiál egy szerviz interfészt, de elfelejt nevet tenni bele, fordítási idejű hibát kapjon? Ha igen, egyszerűen ellenőrizheted a konstans meglétét a szervizt betöltő osztályodban (registry/DIC).
17

Laza kapcsolat

janoszen · 2013. Okt. 18. (P), 13.31
A modulok kozott szeretnek olyan laza kapcsolatot, amennyire csak lehet. Ergo eleg legyen az interfacet megvalositani.
21

Az világos (és nagyon

tgr · 2013. Okt. 18. (P), 21.50
Az világos (és nagyon helyes), de miért kell ehhez a szervizeknek tudniuk a saját nevüket? inf3rnóval vagyok, szerintem rossz irányból közelíted meg a dolgot. Mi van, ha ugyanazt az osztályt több néven akarod elérni, vagy különböző kontextusokban (unit teszt, dev stb) ugyanazon a néven mást akarsz elérni? Ez a konstansozás teljesen merevvé teszi a rendszert, miközben nem látom, hogy lenne bármi előnye.
22

Szvsz sérti az SRP-t és az

inf · 2013. Okt. 19. (Szo), 00.02
Szvsz sérti az SRP-t és az IoC-t, az osztálynak nem szabad semmit tudnia a példányosításáról, azt az IoC container csinálja, így a példányosítás (és az osztály bekonfigurálása) rugalmas marad, mert a container bármikor cserélhető egy másikra.
13

Pedig az enum lesz a jó, ahogy már írták is

Sanyiii · 2013. Okt. 18. (P), 09.07

if (!class_exists('SplEnum')) {
	
	class SplEnum {
		
		protected $value = '';
		
		public function __construct($value = null) {
			if (is_object($value) || is_array($value)) {
				throw new Exception();
			}
			$reflection = new ReflectionObject($this);
			$constants = $reflection->getConstants();
			if (is_null($value)) {
				if (array_key_exists('__DEFAULT', $constants)) {
					$this->value = $constants['__DEFAULT'];
				} else {
					throw new Exception();
				}
			} else {
				if (in_array($value, $constants)) {
					$this->value = $value;
				} else {
					throw new Exception();
				}
			}
		}
		
		public function __toString() {
			return $this->value;
		}
	}
}

class ServiceName extends SplEnum {
	
	const DATABASE = 'database';
}

abstract class AbstractService {
	
	/**
	 * @return ServiceName
	 */
	final public function getName() {
		$name = $this->__getName();
		if (!$name instanceof ServiceName) {
			throw new Exception();
		}
		return $name;
	}
	
	/**
	 * @return ServiceName
	 */
	abstract protected function __getName();
}

abstract class AbstractDatabaseService extends AbstractService {
	
	/**
	 * @return ServiceName
	 */
	protected function __getName() {
		return new ServiceName(ServiceName::DATABASE);
	}
}

class DatabaseService extends AbstractDatabaseService {
	
	// @TODO
}
15

Absztrakt

janoszen · 2013. Okt. 18. (P), 13.28
Pont azt szeretnem elkerulni, hogy kotelezo legyen az absztrakt osztalyt kiterjeszteni.
24

A hobby?

Pepita · 2013. Okt. 19. (Szo), 06.14
Ez "véletlenül" nem a "hobbyprojekted" része lesz? Ha igen, akkor nagyon halványan kapisgálom, hogy mit is szeretnél, de nekem könnyebb lenne specifikáció "Pepitában".
25

Én a helyedben a registry-t

inf · 2013. Okt. 21. (H), 01.08
Én a helyedben a registry-t megpróbálnám okosra írni, és mondjuk lambda függvény alapú erőforrás keresőt tennék bele. Így lehetne interface, egyedi azonosító, vagy bármilyen tetszőleges dolog alapján erőforrást választani. Bár lehet, hogy kalapács van nálam, és mindent szögnek nézek. Most éppen ezt olvasom...
26

A jelenlegi tapasztalataimmal

inf · 2014. Feb. 10. (H), 05.11
A jelenlegi tapasztalataimmal én melegen ajánlom az annotációkat ilyen jellegű metaadatok tárolására.

Erre PHP-ben az egyik lehetséges eszköz a doctrine common-ben lévő annotations modul.

Van még egy tutorial jellegű leírás is hozzá. Meg természetesen van hozzá kód is: doctrine/common. Azért a common-t ajánlom, mert abban van pár alap driver még pluszban, amik segítenek annotációkat feldolgozni.

Itt van egy rövid leírás az általában használt annotációs tervezési mintákról: annotation-based-design-patterns.

Egyelőre még én is kísérletezek egy annotációkat használó rendszerrel, de az eddigiek alapján sokkal jobb, mint bármi más ilyen téren, beleértve a config fájlokat is. Nem tudom te hogy bírod, de nekem nagyon elegem van a string-ként config fájlban megadott osztálynevekből, meg hasonlókból. Elég rosszul refaktorálható az ilyesmi...
27

Csak hogy idézzek a

inf · 2014. Feb. 10. (H), 05.20
Csak hogy idézzek a tervezési mintás linkről:

Service locator
Typical software product consists of modules or services. Components should be able to find other services that provide required functionality. Service as well as tag pattern may be defined as a class that implements specific interface. But contrary to tag interface service interface declares methods.

Before annotations were invented frameworks could locate service if it implemented required interface. This method does not allow distinguishing between two different implementations of the same interface. Annotations allow this. For example Service annotation of Spring Framework can hold service name:

@Service("manager1") 
public class FirstManager implements Manager {}

@Service("manager2") 
public class SecondManager implements Manager {}
This example shows two services that implement the same interface but can be identified by the name written as an annotation argument.

Not only class but also separate method can be a service. For example, each test in the test case is "service" that validates specific testing scenario. Particular test is labeled with annotation @Test and framework can find it.

More complicated example of service pattern is @RequestMapping of Spring MVC. This annotation supports several attributes that help the framework to choose suitable services according to HTTP request parameters. Both classes and methods can be annotated with @RequestMapping.
28

én is kívülre tenném az

szabo.b.gabor · 2014. Feb. 10. (H), 10.01
én is kívülre tenném az elnevezését a szerviznek. pl yii-ben van egy config, amiben megadod, hogy 'db' név alatt mi legyen elérhető. aztán ezt Yii::app()->db - vel el is éred. lehet hogy szebb volna DI-vel, vagy mivel, de a lényegen sokat nem változtat.

csak arra gondolj, hogy mi van ha két adatbázishoz szeretnél csatlakozni.. ezzel a megoldással annyi, hogy két sor lesz a configban db1 és db2 pl.

már ha jól értettem, hogy mit szeretnél.