C # - Могут ли публично наследуемые методы быть скрытыми (например, сделанными закрытыми для производного класса) - PullRequest
54 голосов
/ 20 сентября 2008

Предположим, у меня есть BaseClass с открытыми методами A и B, и я создаю DerivedClass с помощью наследования.

, например

public DerivedClass : BaseClass {}

Теперь я хочу разработать метод C в DerivedClass, который использует A и B. Есть ли способ, которым я могу переопределить методы A и B, чтобы они были частными в DerivedClass, чтобы только метод C был открыт для того, кто хочет использовать мой DerivedClass

Ответы [ 10 ]

68 голосов
/ 20 сентября 2008

Это невозможно, почему?

В C # на вас навязывают, что если вы наследуете публичные методы, вы должны сделать их публичными. В противном случае они ожидают, что вы вообще не будете выходить из класса.

Вместо использования отношения is-a вам придется использовать отношение has-a.

Разработчики языка не позволяют это специально, чтобы вы более правильно использовали наследование.

Например, можно случайно перепутать класс Car с производным от класса Engine, чтобы получить его функциональность. Но двигатель - это функциональность, которую использует автомобиль. Таким образом, вы хотели бы использовать отношения has-a. Пользователь Автомобиля не хочет иметь доступ к интерфейсу Двигателя. И сам Автомобиль не должен путать методы двигателя с его собственными. Ни автомобиль будущих дериваций.

Таким образом, они не позволяют защитить вас от плохой иерархии наследования.

Что делать вместо этого?

Вместо этого вы должны реализовать интерфейсы. Это дает вам возможность использовать функциональность has-a.

Другие языки:

В C ++ вы просто указываете модификатор перед базовым классом private, public или protected. Это делает всех членов базы общедоступными для указанного уровня доступа. Мне кажется глупым, что вы не можете сделать то же самое в C #.

Измененный код:

interface I
{
    void C();
}

class BaseClass
{
    public void A() { MessageBox.Show("A"); }
    public void B() { MessageBox.Show("B"); }
}

class Derived : I
{
    public void C()
    {
        b.A();
        b.B();
    }

    private BaseClass b;
}

Я так понимаю, названия вышеперечисленных классов немного спорят:)

Другие предложения:

Другие предложили сделать A () и B () общедоступными и выбросить исключения. Но это не делает дружественный класс для людей, и это не имеет смысла.

24 голосов
/ 01 июля 2010

Когда вы, например, пытаетесь унаследовать от List<object>, и вы хотите скрыть прямой Add(object _ob) член:

// the only way to hide
[Obsolete("This is not supported in this class.", true)]
public new void Add(object _ob)
{
    throw NotImplementedException("Don't use!!");
}

Это на самом деле не самое предпочтительное решение, но оно делает свою работу. Intellisense по-прежнему принимает, но во время компиляции вы получаете ошибку:

ошибка CS0619: «TestConsole.TestClass.Add (TestConsole.TestObject)» устарел: «Этот класс не поддерживается.»

6 голосов
/ 20 сентября 2008

Звучит как плохая идея. Лисков не впечатлил бы.

Если вы не хотите, чтобы потребители DerivedClass могли иметь доступ к методам DeriveClass.A () и DerivedClass.B (), я бы предложил, чтобы DerivedClass реализовал некоторый общедоступный интерфейс IWhwhatMethodCIsAbout, а потребители DerivedClass должны фактически общаться с Я ничего не знаю о реализации BaseClass или DerivedClass.

5 голосов
/ 20 сентября 2008

Вам нужна композиция, а не наследование.

class Plane
{
  public Fly() { .. }
  public string GetPilot() {...}
}

Теперь, если вам нужен специальный вид Plane, например, у которого есть PairOfWings = 2, но в остальном он делает все, что может плоскость. Вы наследуете плоскость. Этим вы заявляете, что ваше деривация соответствует контракту базового класса и может быть заменена без мерцания везде, где ожидается базовый класс. например LogFlight (Plane) будет продолжать работать с экземпляром BiPlane.

Однако, если вам просто нужно поведение Fly для новой Птицы, которую вы хотите создать и не желаете поддерживать полный контракт базового класса, вы пишете вместо этого. В этом случае рефакторинг поведения методов для повторного использования в новом типе Flight. Теперь создайте и удерживайте ссылки на этот класс в Plane и Bird. Вы не наследуете, потому что Bird не поддерживает полный контракт базового класса ... (например, он не может обеспечить GetPilot ()).

По той же причине вы не можете уменьшить видимость методов базового класса при переопределении. вы можете переопределить и сделать базовый закрытый метод общедоступным в деривации, но не наоборот. например В этом примере, если я получу тип плоскости «BadPlane», а затем переопределю и «Скрыть» GetPilot () - сделаю его закрытым; клиентский метод LogFlight (Plane p) будет работать для большинства плоскостей, но будет взорван для «BadPlane», если для реализации LogFlight потребуется / вызов GetPilot (). Поскольку ожидается, что все деривации базового класса будут «заменяемыми» везде, где ожидается параметр базового класса, это следует запретить.

3 голосов
/ 20 сентября 2008

@ Брайан Р. Бонди указал мне на интересную статью о сокрытии через наследование и ключевое слово new .

http://msdn.microsoft.com/en-us/library/aa691135(VS.71).aspx

Так что в качестве обходного пути я бы предложил:

class BaseClass
{
    public void A()
    {
        Console.WriteLine("BaseClass.A");
    }

    public void B()
    {
        Console.WriteLine("BaseClass.B");
    }
}

class DerivedClass : BaseClass
{
    new public void A()
    {
        throw new NotSupportedException();
    }

    new public void B()
    {
        throw new NotSupportedException();
    }

    public void C()
    {
        base.A();
        base.B();
    }
}

Этот код, подобный этому, выдаст NotSupportedException :

    DerivedClass d = new DerivedClass();
    d.A();
3 голосов
/ 20 сентября 2008

Единственный известный мне способ сделать это - использовать отношение Has-A и реализовывать только те функции, которые вы хотите предоставить.

1 голос
/ 20 сентября 2008

Я бы сказал, что если у вас есть кодовая база, с которой вы хотите это сделать, это не самая лучшая кодовая база. Обычно это признак того, что классу одного уровня иерархии требуется определенная публичная подпись, в то время как другому классу, производному от этого класса, это не нужно.

Предстоящая парадигма кодирования называется «Композиция над наследованием». Это напрямую противоречит принципам объектно-ориентированной разработки (особенно принципу единой ответственности и принципу открытой / закрытой).

К сожалению, из-за того, что многие из нас, разработчиков, учились объектной ориентации, у нас сформировалась привычка немедленно думать о наследовании, а не о композиции. У нас, как правило, большие классы, на которые возложено много разных обязанностей просто потому, что они могут содержаться в одном и том же объекте «Реальный мир». Это может привести к иерархии классов более 5 уровней.

К сожалению, побочный эффект, о котором разработчики обычно не думают при работе с наследованием, заключается в том, что наследование является одной из самых сильных форм зависимостей, которые вы когда-либо можете ввести в свой код. Ваш производный класс теперь сильно зависит от класса, от которого он был унаследован. Это может сделать ваш код более хрупким в долгосрочной перспективе и привести к сложным проблемам, когда изменение определенного поведения в базовом классе нарушает производные классы непонятными способами.

Один из способов взломать ваш код - использовать интерфейсы, подобные упомянутым в другом ответе. В любом случае это разумно, так как вы хотите, чтобы внешние зависимости класса связывались с абстракциями, а не с конкретными / производными типами. Это позволяет вам изменять реализацию без изменения интерфейса, и все это без влияния на строку кода в вашем зависимом классе.

Я бы предпочел не поддерживать систему с сотнями / тысячами и даже большим количеством классов, которые все малы и слабо связаны, чем иметь дело с системой, которая интенсивно использует полиморфизм / наследование и имеет меньше классов, которые более тесно связаны ,

Возможно, лучший лучший ресурс по объектно-ориентированной разработке - это книга Роберта Мартина Agile Software Development, Принципы, Шаблоны и Практики .

1 голос
/ 20 сентября 2008

Прятаться - довольно скользкий склон. Основными вопросами, ИМО, являются:

  • Это зависит от времени разработки тип объявления экземпляра, это означает, что если вы делаете что-то вроде BaseClass obj = new SubClass (), затем вызовите obj.A (), сокрытие побеждено. BaseClass.A () будет выполнен.

  • Скрытие может очень легко затенить поведение (или изменения поведения) в базовый тип. Это очевидно меньше беспокойства, когда вы владеете обоими стороны уравнения, или если вызов base.xxx является частью вашего подэлемента.

  • Если вы на самом деле действительно владеете обеими сторонами уравнения базового / подкласса, тогда вы сможете разработать более управляемое решение, чем институционализированное сокрытие / затенение.
0 голосов
/ 27 февраля 2018

Хотя ответом на вопрос является «нет», есть один совет, на который я хотел бы обратить внимание, чтобы другие, прибывшие сюда, (учитывая, что OP как бы намекает на доступ к сборке третьими лицами). Когда другие ссылаются на сборку, Visual Studio должен учитывать следующий атрибут, чтобы он не отображался в intellisense (скрытый, но можно вызывать STILL, поэтому будьте осторожны):

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

Если у вас не было другого выбора, вы сможете использовать new для метода, который скрывает метод базового типа, вернуть => throw new NotSupportedException(); и объединить его с указанным выше атрибутом.

Другой трюк зависит от НЕ наследования от базового класса, если это возможно, где база имеет соответствующий интерфейс (например, IList<T> для List<T>). Реализация интерфейсов «явно» также скрывает эти методы от intellisense для типа класса. Например:

public class GoodForNothing: IDisposable
{
    void IDisposable.Dispose() { ... }
}

В случае var obj = new GoodForNothing() метод Dispose() будет недоступен для obj. Тем не менее, он будет доступен любому, кто явно набирает obj до IDisposable.

Кроме того, вы можете обернуть базовый тип вместо наследования от него, а затем скрыть некоторые методы:

public class MyList<T> : IList<T>
{
    List<T> _Items = new List<T>();
    public T this[int index] => _Items[index];
    public int Count => _Items.Count;
    public void Add(T item) => _Items.Add(item);
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    void ICollection<T>.Clear() => throw new InvalidOperationException("No you may not!"); // (hidden)
    /*...etc...*/
}
0 голосов
/ 20 сентября 2008

Если они определены как общедоступные в исходном классе, вы не можете переопределить их как частные в вашем производном классе. Однако вы можете сделать так, чтобы открытый метод вызывал исключение и реализовывал вашу собственную частную функцию.

Редактировать: Хорхе Феррейра прав.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...