Czy przy projektowaniu klasy należy preferować spójność w zachowaniu zamiast powszechnej praktyki programowania? Aby podać konkretny przykład:
Powszechna konwencja jest następująca: jeśli klasa jest właścicielem obiektu (np. Go utworzył), jest odpowiedzialna za jego wyczyszczenie po zakończeniu. Konkretnym przykładem może być .NET, że jeśli twoja klasa jest właścicielem IDisposable
obiektu, powinna go pozbyć pod koniec swojego życia. A jeśli nie jesteś właścicielem, nie dotykaj go.
Teraz, jeśli spojrzymy na StreamWriter
klasę w .NET, możemy znaleźć w dokumentacji, że zamyka ona strumień bazowy, gdy jest on zamykany / usuwany. Jest to konieczne w przypadkach, gdy StreamWriter
tworzenie instancji następuje poprzez przekazanie nazwy pliku, ponieważ program piszący tworzy podstawowy strumień plików i dlatego musi go zamknąć. Można jednak również przekazać strumień zewnętrzny, który pisarz również zamyka.
Denerwowało mnie to mnóstwo razy (tak, wiem, że możesz zrobić nie zamykające się opakowanie, ale nie o to chodzi), ale najwyraźniej Microsoft podjął decyzję, że bardziej konsekwentne jest zamykanie strumienia bez względu na to, skąd pochodzi.
Kiedy spotykam się z takim wzorcem w jednej z moich klas, zwykle tworzę ownsFooBar
flagę, która jest ustawiana na fałsz w przypadkach, gdy FooBar
jest wstrzykiwana przez konstruktor, i tak naprawdę jest inaczej. W ten sposób odpowiedzialność za jego wyczyszczenie przechodzi na osobę dzwoniącą, gdy przekazuje instancję w sposób jawny.
Teraz zastanawiam się, czy być może spójność powinna być lepsza niż najlepsza praktyka (a może moja najlepsza praktyka nie jest tak dobra)? Jakieś argumenty za / przeciw?
Edytuj dla wyjaśnienia
Przez „spójność” mam na myśli: konsekwentne zachowanie klasy zawsze przejmującej własność (i zamykającą strumień) w porównaniu z „najlepszą praktyką”, aby przejąć własność obiektu tylko wtedy, gdy go utworzyłeś lub wyraźnie przekazałeś własność.
Na przykład, w którym jest przyjemny:
Załóżmy, że masz dwie podane klasy (z biblioteki innej firmy), które akceptują strumień, aby coś z nim zrobić, na przykład tworzenie i przetwarzanie niektórych danych:
public class DataProcessor
{
public Result ProcessData(Stream input)
{
using (var reader = new StreamReader(input))
{
...
}
}
}
public class DataSource
{
public void GetData(Stream output)
{
using (var writer = new StreamWriter(output))
{
....
}
}
}
Teraz chcę użyć tego w następujący sposób:
Result ProcessSomething(DataSource source)
{
var processor = new DataProcessor();
...
var ms = new MemoryStream();
source.GetData(ms);
return processor.ProcessData(ms);
}
To się nie powiedzie z wyjątkiem Cannot access a closed stream
w procesorze danych. Jest nieco skonstruowany, ale powinien zilustrować tę kwestię. Istnieją różne sposoby rozwiązania tego problemu, ale wydaje mi się, że pracuję nad czymś, czego nie powinienem.
Odpowiedzi:
Używam tej techniki, nazywam ją „handoff” i uważam, że zasługuje ona na status wzorca. Gdy obiekt A akceptuje obiekt jednorazowy B jako parametr czasu budowy, przyjmuje również wartość logiczną o nazwie „handoff” (która domyślnie przyjmuje wartość false), a jeśli to prawda, wówczas usuwanie kaskady A do dyspozycji B.
Nie opowiadam się za rozpowszechnianiem niefortunnych wyborów innych ludzi, więc nigdy nie zaakceptuję złych praktyk Microsoftu jako ustalonej konwencji, ani też nie uważam ślepego podążania za niefortunnymi wyborami Microsoftu za „konsekwentne zachowanie” w jakikolwiek sposób, kształcie lub formie .
źródło
handOff
wzorca, jeśli taka właściwość jest ustawiona w konstruktorze, ale istnieje inna metoda jej modyfikacji, ta inna metoda powinna również zaakceptowaćhandOff
flagę. JeślihandOff
określono dla aktualnie przechowywanego przedmiotu, należy go zutylizować, a następnie nowy przedmiot powinien zostać zaakceptowany, a wewnętrznahandOff
flaga zaktualizowana, aby odzwierciedlić status nowego przedmiotu. Metoda nie powinna sprawdzać równości starych i nowych odniesień, ponieważ jeśli pierwotne odwołanie zostało „przekazane”, wszystkie odniesienia przechowywane przez pierwotnego dzwoniącego powinny zostać porzucone.Dispose
żądania zostałyby zignorowane dla szczotek systemowych, metoda „color.CreateBrush” może w dowolnym momencie utworzyć nowy pędzel (co wymagałoby usunięcia) lub zwrócić pędzel systemowy (który zignorowałby usunięcie). Najprostszy sposób, aby zapobiec ponowne użycie przedmiotu, jeśli zależy mu na usunięciu, ale zezwolenie na ponowne użycie, jeśli nie, to usunięcie goSzczerze mówiąc, myślę, że masz rację. Myślę, że Microsoft pomylił się z klasą StreamWriter, w szczególności z powodów, które opisujesz.
Jednak od tamtej pory widziałem dużo kodu, w którym ludzie nawet nie próbują pozbyć się własnego strumienia, ponieważ framework zrobi to za nich, więc naprawienie go teraz wyrządzi więcej szkody niż pożytku.
źródło
Poszedłbym z konsekwencją. Jak wspomniałeś większości ludzi, sensowne jest, że jeśli twój obiekt stworzy obiekt potomny, będzie go właścicielem i zniszczy. Jeśli otrzyma odniesienie z zewnątrz, w pewnym momencie „na zewnątrz” również trzyma ten sam przedmiot i nie powinien być własnością. Jeśli chcesz przenieść własność z zewnątrz na swój obiekt, skorzystaj z metod Dołącz / Odłącz lub konstruktora, który akceptuje wartość logiczną, która wyjaśnia, że własność została przeniesiona.
Po wpisaniu tego wszystkiego, aby uzyskać pełną odpowiedź, myślę, że większość ogólnych wzorców projektowych niekoniecznie należałoby do tej samej kategorii co klasy StreamWriter / StreamReader. Zawsze myślałem, że powodem, dla którego MS zdecydowało się na automatyczne przejęcie własności przez StreamWriter / Reader, jest to, że w tym konkretnym przypadku współwłasność nie ma większego sensu. Nie można jednocześnie odczytywać / zapisywać dwóch różnych obiektów z tego samego strumienia. Jestem pewien, że mógłbyś napisać coś w rodzaju deterministycznego podziału czasu, ale byłoby to piekło anty-wzorca. Prawdopodobnie dlatego powiedzieli, że ponieważ nie ma sensu udostępniać strumienia, po prostu zawsze go przejmijmy. Ale nie uważałbym, aby takie zachowanie kiedykolwiek stało się ogólną konwencją dla wszystkich klas.
Można uznać strumienie za spójny wzorzec, w którym przekazywany obiekt jest nie do udostępnienia z punktu widzenia projektowania, a zatem bez żadnych dodatkowych flag, czytelnik / pisarz po prostu przejmuje jego własność.
źródło
Bądź ostrożny. To, co uważasz za „konsekwencję”, może być przypadkowym zbiorem nawyków.
„Koordynacja interfejsów użytkownika w celu zapewnienia spójności”, SIGCHI Bulletin 20, (1989), 63–65. Przepraszamy, brak linku, to stary artykuł. „... dwudniowe warsztaty z udziałem 15 ekspertów nie były w stanie wypracować definicji spójności”. Tak, chodzi o „interfejs”, ale uważam, że jeśli pomyślisz o „spójności” przez jakiś czas, okaże się, że nie możesz jej zdefiniować.
źródło