Jakie są dobre powody, aby używać jawnego wdrożenia interfejsu wyłącznie w celu ukrywania członków?

11

Podczas jednego z moich badań nad zawiłościami języka C # natknąłem się na interesujący fragment dotyczący jawnego wdrożenia interfejsu.

While this syntax is quite helpful when you need to resolve name clashes, you can use explicit interface implementation simply to hide more "advanced" members from the object level.

Różnica między zezwalaniem na użycie object.method()lub wymaganiem rzutu ((Interface)object).method()wydaje mi się niedorzecznym zaciemnieniem dla moich niedoświadczonych oczu. Tekst zauważyć, że ukryje metodę z IntelliSense na poziomie obiektu, ale dlaczego ty chcesz zrobić, że jeśli to nie było konieczne, aby uniknąć konfliktów nazw?

Natanus
źródło

Odpowiedzi:

12

Wyobraź sobie sytuację, w której interfejs zmusza klasę do implementacji metod, które w rzeczywistości nie mają sensu jako części klasy. Może się to często zdarzyć, gdy interfejsy są szczególnie duże i naruszają zasadę segregacji interfejsów.

Ta klasa musi implementować interfejs, ale zamiast zanieczyszczać jego dobrze widoczny interfejs publiczny metodami, które nie mają sensu, można jawnie zaimplementować te metody, które nie powinny być oczywiście dostępne. Wysoce widoczny interfejs publiczny klasy określa sposób, w jaki klasa ma być używana, a jawna implementacja ma miejsce, gdy dostęp do klasy uzyskuje się przez interfejs.

Chris Pitman
źródło
6
Czy to tylko ja, czy to brzmi jak okropny pomysł?
Kevin Peno,
5
@Kevin, jak to? Czasami interfejs wymyka się spod kontroli i trzeba go używać. Łagodzenie szkód narzucających się na interfejs wydaje się rozsądne.
Chris Pitman,
1
+1 za wskazanie, że wiele razy prawdziwy świat przeszkadza we właściwym sposobie robienia rzeczy. @Kevin tak to fatalny pomysł, ale czasami nie masz wyboru i muszą pracować z odstojnika - lepiej, aby ukryć ją i zrobić fasadę robi rzeczy poprawnie, gdy nie można naprawdę zrobić to poprawnie.
Wayne Molina
@Wayne, rozumiem twoje rozumowanie, że chcesz / korzystasz z takiego systemu. Do diabła, prawdopodobnie użyłbym tego do dokładnie tego, co podałeś. Wydaje mi się, że mój krótki komentarz naprawdę wskazywał, że jest to niewłaściwe, więc po co pozwalać na to w języku.
Kevin Peno
1
Nadmierna separacja interfejsów może być główną przeszkodą w składaniu i agregacji. Rozważmy na przykład, że ktoś chce mieć implementację, IEnumerable<T>która zachowuje się jak połączenie dwóch innych. Jeśli IEnumerable<T>zawiera właściwość określającą, czy jej liczba była znana, potencjalnie nieskończona, czy też żadna, wraz z metodą zapytania o jej liczbę, klasa, która agreguje wielokrotność IEnumerable<T>i zachowuje się jak konkatenacja, mogłaby skutecznie zgłosić liczbę w przypadku, gdy niektóre kapsułkowane kolekcje znały ich liczbę.
supercat
4

Najbardziej użyteczną aplikacją, którą znalazłem, jest wdrażanie fabryk. W wielu przypadkach przydatne jest tworzenie klas, które można modyfikować w fabryce, ale niezmienne dla klas zewnętrznych. Można to łatwo wdrożyć w Javie za pomocą klas wewnętrznych, ustawiając pola i ich ustawiacze jako prywatne i ujawniając gettery jako członków publicznych. Jednak w C # musiałem użyć jawnych interfejsów, aby osiągnąć to samo. Wyjaśnię dalej:

W Javie klasa wewnętrzna ORAZ klasa zewnętrzna mogą uzyskiwać dostęp do prywatnych członków siebie, co ma całkowity sens, ponieważ klasy są ze sobą ściśle powiązane. Są w tym samym pliku kodu i prawdopodobnie zostały opracowane przez tego samego programistę. Oznacza to, że fabryki mogą nadal uzyskiwać dostęp do prywatnych pól i metod w klasie wewnętrznej w celu modyfikacji ich wartości. Ale klasy zewnętrzne nie będą miały dostępu do tych pól, chyba że za pośrednictwem ich publicznych modułów pobierających.

Jednak w języku C # klasy zewnętrzne nie mogą uzyskać dostępu do prywatnych członków klas wewnętrznych, więc koncepcja nie ma bezpośredniego zastosowania. Użyłem jawnego interfejsu jako obejścia, definiując prywatny interfejs w klasie zewnętrznej i jawnie implementując go w klasie wewnętrznej. W ten sposób tylko klasa zewnętrzna może uzyskać dostęp do metod w tym interfejsie w taki sam sposób jak w Javie (ale muszą to być metody, a nie pola).

Przykład:

public class Factory
{
    // factory method to create a hard-coded Mazda Tribute car.
    public static Car CreateCar()
    {
        Car car = new Car();

        // the Factory class can modify the model because it has access to
        // the private ICarSetters interface
        ((ICarSetters)car).model = "Mazda Tribute";

        return car;
    }

    // define a private interface containing the setters.
    private interface ICarSetters
    {
        // define the setter in the private interface
        string model { set; }
    }

    // This is the inner class. It has a member "model" that should not be modified
    // but clients, but should be modified by the factory.
    public class Car: ICarSetters
    {
        // explicitly implement the setter
        string ICarSetters.model { set; }

        // create a public getter
        public string model { get; }
    }
}

class Client
{
    public Client()
    {
        Factory.Car car = Factory.CreateCar();

        // can only read model because only the getter is public
        // and ICarSetters is private to Factory
        string model = car.model;
    }
}

Do tego użyłbym jawnych interfejsów.

Elias
źródło
3

Jeśli klasa implementuje dwa interfejsy zawierające element członkowski z tym samym podpisem, wówczas implementacja tego elementu w klasie spowoduje, że oba interfejsy będą używać tego elementu jako swojej implementacji. W poniższym przykładzie wszystkie wywołania w celu Paintwywołania tej samej metody. DO#

class Test 
{
    static void Main()

    {
        SampleClass sc = new SampleClass();
        IControl ctrl = (IControl)sc;
        ISurface srfc = (ISurface)sc;

        // The following lines all call the same method.
        sc.Paint();
        ctrl.Paint();
        srfc.Paint();
    }
}


interface IControl
{
    void Paint();
}
interface ISurface
{
    void Paint();
}
class SampleClass : IControl, ISurface
{
    // Both ISurface.Paint and IControl.Paint call this method. 
    public void Paint()
    {
        Console.WriteLine("Paint method in SampleClass");
    }
}

Natomiast wyraźna implementacja jednego (lub obu) z nich pozwala określić różne zachowania dla każdego z nich.

Muhammad Tayyeb
źródło
1

Jawne interfejsy są przydatne, gdy albo obiekt ma dwie lub więcej bardzo wyraźnych form komunikacji.

Na przykład obiekt, który utrzymuje kolekcję obiektów potomnych, które muszą komunikować się z rodzicem.

Są pewne akcje, które chcemy, aby były dostępne tylko dla zewnętrznych rozmówców, a niektóre akcje, które chcemy, aby były dostępne tylko dla obiektów potomnych.

Jawne interfejsy pomagają zapewnić ten podział.

    static void Main(string[] args)
    {
        var parent = new Parent();
        parent.DoExternalStuff();
    }

    interface IExternal {
        void DoExternalStuff();
    }

    interface IInternal {
        void DoInternalStuff();
    }

    class Parent : IExternal, IInternal
    {
        public Parent()
        {
            var child = new Child(this);
        }

        public void DoExternalStuff()
        {
           // do something exciting here for external callers
        }

        void IInternal.DoInternalStuff()
        {
            // do something exciting here for internal callers
        }
    }

    class Child
    {
        public Child(IInternal parent)
        {
            parent.DoInternalStuff();
        }
    }
lzcd
źródło
0

Być może, jeśli masz funkcjonalność, która była przydatna dla użytkowników, ale tylko wtedy, gdy naprawdę wiesz, co robisz (wystarczy przeczytać dokumentację, aby dowiedzieć się o tej funkcji), ale może to popsuć, jeśli tego nie zrobisz.

Dlaczego miałbyś to zrobić zamiast oferować, powiedzmy drugi „interfejs zaawansowanych funkcji”, nie wiem.

Malcolm
źródło
0

Kod jest na ogół czytany więcej niż jest napisany i używam Intellisense tylko do szybkiego pisania kodu. Nie pisałbym kodu w szczególny sposób, aby Intellisense był ładniejszy.

Nie bardzo rozumiem jak

Obiekt ((Interfejs)) .method () jest bardziej czytelny niż obiekt.method ().

Gdybym miał wiele metod na danym obiekcie, mógłbym zapytać, czy jest to obiekt boga i czy rzeczywiście powinien zostać podzielony na wiele prostszych obiektów.

Peter Smith
źródło