2010-05-10 11 views
13

Rozważam dwa różne projekty klas do obsługi sytuacji, w której niektóre repozytoria są tylko do odczytu, a inne są do odczytu i zapisu. (I nie przewidują żadnych potrzebę tylko do zapisu repozytorium.)Który jest lepszy projekt klasy C# do czynienia z czytaniem i pisaniem w porównaniu tylko do odczytu


Klasa Projekt 1 - zapewnić pełną funkcjonalność w klasie bazowej, a następnie wystawiać zastosowanie funkcjonalności publicznie w klasach podrzędnych

public abstract class RepositoryBase 
{ 
    protected virtual void SelectBase() { // implementation... } 
    protected virtual void InsertBase() { // implementation... } 
    protected virtual void UpdateBase() { // implementation... } 
    protected virtual void DeleteBase() { // implementation... } 
} 

public class ReadOnlyRepository : RepositoryBase 
{ 
    public void Select() { SelectBase(); } 
} 

public class ReadWriteRepository : RepositoryBase 
{ 
    public void Select() { SelectBase(); } 
    public void Insert() { InsertBase(); } 
    public void Update() { UpdateBase(); } 
    public void Delete() { DeleteBase(); } 
} 

Klasa Projekt 2 - klasa odczytu dziedziczy tylko do odczytu klasy

public class ReadOnlyRepository 
{ 
    public void Select() { // implementation... } 
} 

public class ReadWriteRepository : ReadOnlyRepository 
{ 
    public void Insert() { // implementation... } 
    public void Update() { // implementation... } 
    public void Delete() { // implementation... } 
} 

Czy jeden z tych projektów jest wyraźnie silniejszy od drugiego? Jeśli tak, to który i dlaczego?

P.S. Jeśli to brzmi jak pytanie o pracę domową, nie jest, ale możesz go używać jako jednego, jeśli chcesz :)

+1

Dlaczego konieczne jest rozróżnienie dwóch rodzajów repozytorium? –

+0

@John, cóż, być może nie, ale myślałem, że chcę się upewnić, że chronię tabele bazy danych, które nie powinny się zmieniać od przypadkowych zmian. – devuxer

+1

@DanM: co DBA ma do powiedzenia na ten temat? Większość poczuje, że miejsce do ochrony bazy danych znajduje się w bazie danych, a nie w kodzie. –

Odpowiedz

25

Jak o trzeciej opcji, ściśle związany z pierwszym, ale przy użyciu interfejsów zamiast:

public interface IReadRepository { 
    public void Select(); 
} 

public interface IWriteRepository { 
    public void Insert(); 
    public void Update(); 
    public void Delete(); 
} 

// Optional 
public interface IRepository : IReadRepository, IWriteRepository { 
} 

public class Repository : IRepository { 
    // Implementation 
} 

ten sposób realizacja jest (lub może być) wszystko w jednym miejscu, a rozróżnienie tylko przez na który interfejs patrzysz.

+1

+1. Bardzo podoba mi się to podejście *, ponieważ metoda może dokładnie określić, jaki rodzaj dostępu do "repozytorium" potrzebuje: jeśli dostęp tylko do odczytu jest wystarczający, wymaga 'IReadRepository'; jeśli wymaga dostępu do zapisu, wymaga 'IWriteRepository'; jeśli potrzebuje dostępu do odczytu i zapisu, wymaga "IRepository". Ciekawym efektem ubocznym tego jest to, że definicja 'get' i' set' może zostać podzielona na dwa interfejsy. – stakx

+0

Myślę, że jest to interesujący wzorzec, ponieważ można wymusić co najmniej "semantyczny" rodzaj zabezpieczenia, dając obiektom w swoim programie, które "potrzebują dostępu do zapisu i są wyczyszczone do zapisu", dostęp do "zapisywalnych" interfejsów.Nie pomyślałbym o tym tylko jako o bezpieczeństwie (ponieważ problem bezpieczeństwa dotyczy problemu z bazą danych), ale także jako "zminimalizowanie ile interfejsu dajesz różnym obiektom w systemie", jako dobrej zasadzie OOP. –

+0

Ale nadal, chyba że domena wymaga klas WriteOnly, połączyłbym 2 interfejsy (IWriteRepository: IReadRepository), co oznacza, że ​​pytanie dziedziczenia zostało przeniesione na interfejsy. –

0

Z pewnością powiedziałbym, że projekt 2 jest najsilniejszy. Jeśli chcesz mieć implementację tylko do odczytu, nie musisz nic wiedzieć o pisaniu. Rozsądnie jest rozszerzyć implementację tylko do odczytu dzięki metodom wstawiania, aktualizowania i usuwania. Myślę też, że ten wzór najbardziej pasuje do Open-Closed principle.

5

(Edycja:.. myślę Eric Petroelje oferuje bardzo ładne rozwiązanie interfejsu opartego na jego odpowiedź będzie prawdopodobnie głosować za jego sugestią, przede wszystkim)

Z twoich dwóch opcji, ja wyraźnie głosowałoby za projektem nr 2: .

Z projektu # 1, myślę, że to nie ma sensu mieć klasę „tylko do odczytu”, która wewnętrznie nie jest tylko do odczytu w ogóle:

  1. dane prędkości odczytu tylko klasa jest "cięższa" niż powinna.

  2. Każdy może pochodzić z klasy tylko do odczytu, a następnie wywołać dowolną metodę modyfikacji klasy podstawowej. Przynajmniej, z projektem nr 1, powinieneś utworzyć klasę tylko do odczytu sealed.

design # 2, jest znacznie jaśniejsze niż klasa tylko do odczytu jest zmniejszona wersja (klasa podstawowa) w pełni funkcjonalny klasę lub sformułowane inaczej.

+0

Tak. Właśnie taki projekt wykorzystałem w moich projektach, które wymagały osobnych interfejsów RW i R. –

+1

Myślę, że ja też wolę rozwiązanie Erica, ale +1 dla twojego punktu # 2. To kwestia, o której nie myślałem. – devuxer

1

Po pierwsze, pozwól mi przyznać, że przyjmuję pewne założenia dotyczące tego, co możesz zrobić.Jeśli to nie trafi w sedno, daj mi znać.

Nie jestem pewien, jak przydatne będą zajęcia w jednej z dwóch opcji. Zakładam, że wywoływałbyś kod, który używałby instancji repozytorium tylko do odczytu, aw innych przypadkach instancji repozytorium do odczytu/zapisu, ale interfejsy nie pasują, więc i tak musiałbyś rozróżnić kod?

Lepszym rozwiązaniem może być udostępnienie wspólnego interfejsu, a następnie wyrzucanie wyjątków, jeśli spróbujesz napisać do repozytorium, gdy zostanie on przeczytany, a Twój kod będzie obsługiwał wyjątki.

1

Powiedziałbym projekt # 2, ale wtedy powinieneś zmienić nazwę ReadOnlyRepository na coś w stylu ReadRepository.

Dziedziczenie definiuje relację IS-A między klasami, a wyrażenie "ReadWriteRepository to ReadOnlyRepository" nie brzmi logicznie. Ale robi to "ReadWriteRepository is ReadingRepository".

0

Sugerowałbym posiadanie klasy bazowej ReadableFoo, z zamkniętą pochodną klasy ImmutableFoo, której konstruktor pobiera ReadableFoo, i ewentualnie dziedziczoną pochodną klasy MutableFoo, i ewentualnie klasę ReadableShadowFoo, której konstruktor wymagałby ReadableFoo (który może lub może nie może być zmienna), ale który działałby jako opakowanie tylko do odczytu.

0

Odpowiedź Eric oznacza SOLIDną zasadę ISP. Jest tak prosty i łatwy w użyciu.