Dla mnie ma to sens, ponieważ nadaje konkretną nazwę klasie, zamiast polegać na ogólnej nazwie NamedEntity. Z drugiej strony istnieje wiele takich klas, które po prostu nie mają żadnych dodatkowych właściwości.
Czy są jakieś wady tego podejścia?
Podejście nie jest złe, ale dostępne są lepsze rozwiązania. Krótko mówiąc, interfejs byłby znacznie lepszym rozwiązaniem. Głównym powodem, dla którego interfejsy i dziedziczenie są różne, jest to, że można dziedziczyć tylko z jednej klasy, ale można zaimplementować wiele interfejsów .
Weźmy na przykład pod uwagę, że nazwano byty i poddano inspekcji. Masz kilka podmiotów:
One
nie jest podmiotem kontrolowanym ani podmiotem nazwanym. To proste:
public class One
{ }
Two
jest jednostką nazwaną, ale nie kontrolowaną. Zasadniczo masz to teraz:
public class NamedEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Two : NamedEntity
{ }
Three
jest zarówno wpisem nazwanym, jak i kontrolowanym. Tutaj napotykasz problem. Możesz utworzyć AuditedEntity
klasę podstawową, ale nie możesz Three
dziedziczyć zarówno, jak AuditedEntity
i NamedEntity
:
public class AuditedEntity
{
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
public class Three : NamedEntity, AuditedEntity // <-- Compiler error!
{ }
Możesz jednak pomyśleć o obejściu, AuditedEntity
odziedzicząc po NamedEntity
. Jest to sprytny hack, aby upewnić się, że każda klasa musi dziedziczyć (bezpośrednio) od jednej innej klasy.
public class AuditedEntity : NamedEntity
{
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
public class Three : AuditedEntity
{ }
To nadal działa. Ale to, co tutaj zrobiłeś, stwierdza, że każda kontrolowana jednostka jest z natury również jednostką nazwaną . Co prowadzi mnie do mojego ostatniego przykładu. Four
jest kontrolowaną jednostką, ale nie jednostką nazwaną. Ale nie możesz pozwolić na Four
dziedziczenie, AuditedEntity
tak jak NamedEntity
robiłbyś to z powodu dziedziczenia między AuditedEntity and
NamedEntity`.
Korzystając z dziedziczenia, nie ma sposobu na zrobienie obu Three
i Four
działanie, chyba że zaczniesz duplikować klasy (co otwiera zupełnie nowy zestaw problemów).
Za pomocą interfejsów można to łatwo osiągnąć:
public interface INamedEntity
{
int Id { get; set; }
string Name { get; set; }
}
public interface IAuditedEntity
{
DateTime CreatedOn { get; set; }
DateTime UpdatedOn { get; set; }
}
public class One
{ }
public class Two : INamedEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Three : INamedEntity, IAuditedEntity
{
public int Id { get; set; }
public string Name { get; set; }
DateTime CreatedOn { get; set; }
DateTime UpdatedOn { get; set; }
}
public class Four : IAuditedEntity
{
DateTime CreatedOn { get; set; }
DateTime UpdatedOn { get; set; }
}
Jedyną niewielką wadą jest to, że nadal musisz wdrożyć interfejs. Ale zyskujesz wszystkie korzyści z posiadania wspólnego typu wielokrotnego użytku, bez żadnych wad, które pojawiają się, gdy potrzebujesz odmian wielu wspólnych typów dla danej jednostki.
Ale twój polimorfizm pozostaje nienaruszony:
var one = new One();
var two = new Two();
var three = new Three();
var four = new Four();
public void HandleNamedEntity(INamedEntity namedEntity) {}
public void HandleAuditedEntity(IAuditedEntity auditedEntity) {}
HandleNamedEntity(one); //Error - not a named entity
HandleNamedEntity(two);
HandleNamedEntity(three);
HandleNamedEntity(four); //Error - not a named entity
HandleAuditedEntity(one); //Error - not an audited entity
HandleAuditedEntity(two); //Error - not an audited entity
HandleAuditedEntity(three);
HandleAuditedEntity(four);
Z drugiej strony istnieje wiele takich klas, które po prostu nie mają żadnych dodatkowych właściwości.
Jest to odmiana wzorca interfejsu znacznika , w której implementuje się pusty interfejs wyłącznie po to, aby móc użyć typu interfejsu do sprawdzenia, czy dana klasa jest „oznaczona” tym interfejsem.
Używasz klas odziedziczonych zamiast zaimplementowanych interfejsów, ale cel jest taki sam, więc zamierzam nazywać to „klasą oznaczoną”.
Na pierwszy rzut oka nie ma nic złego w interfejsach / klasach znaczników. Są poprawne pod względem składniowym i technicznym i nie ma nieodłącznych wad ich używania, pod warunkiem, że marker jest ogólnie prawdziwy (w czasie kompilacji) i nie jest warunkowy .
Właśnie tak należy rozróżniać różne wyjątki, nawet jeśli wyjątki te nie mają żadnych dodatkowych właściwości / metod w porównaniu z metodą podstawową.
Nie ma w tym nic złego, ale radziłbym to robić ostrożnie, upewniając się, że nie tylko próbujesz ukryć istniejący błąd architektoniczny źle zaprojektowanym polimorfizmem.
OrderDateInfo
s, od tych, które są istotne dla innychNamedEntity
s