2008-09-19 2 views
48

Предположим, что у меня есть BaseClass с общедоступными методами A и B, и я создаю DerivedClass через наследование.C# - Можно ли скрывать скрытые методы (например, сделать частные для производного класса)

например.

public DerivedClass : BaseClass {} 

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

ответ

64

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

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

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

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

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

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

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

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

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

В C++ вы просто указать модификатор до базового класса частных, общественных или защищены. Это делает все члены базы доступными для этого уровня доступа. Мне кажется глупым, что вы не можете сделать то же самое в 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; 
} 

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

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

предложили

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

0

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

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

3

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

1

@Brian R. Bondy указал мне на интересную статью о Скрытии через наследование и ключевое слово .

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(); 
+1

Если вы используете DerivedClass d = новые DerivedClass(); d.A(), вы все равно сможете получить доступ к реализации базового класса A(). – 2008-09-19 23:41:17

+0

Хорхе, я просто попробовал это, и, как указал Брайан, А и Б все равно будут выставлены. – Lyndon 2008-09-19 23:42:44

+0

http://msdn.microsoft.com/en-us/library/aa691135(VS.71).aspx – 2008-09-19 23:44:10

6

Это звучит как плохая идея. Liskov не будет впечатлен.

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

1

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

  • Это зависит от времени разработки декларации типа, например, смысл, если вы делаете что-то вроде BaseClass OBJ = новый подклассу(), а затем вызова OBJ. A(), сокрытие побеждено. Запускается BaseClass.A().

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

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

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

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

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

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

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

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

Возможно, best Ресурс для объектно-ориентированного развития - это книга Роберта К. Мартина, Agile Software Development, Principles, Patterns, and Practices.

5

Что вам нужно, это состав не наследование.

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

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

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

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

16

Когда вы, например, пытаются наследовать от 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 еще принимает, но во время компиляции вы получите ошибку:

error CS0619: 'TestConsole.TestClass.Add(TestConsole.TestObject)' is obsolete: 'This is not supported in this class.'

0

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

[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...*/ 
} 
Смежные вопросы