Абстрактный дизайн класса - PullRequest
4 голосов
/ 23 декабря 2009

Это приемлемый дизайн ??

Абстрактный класс

public abstract class SomethingBase
{ 
   public abstract int Method1(int number1);
   public abstract int Method2(int number2); 
} 

public class Class1 : SomethingBase
{
   public override int Method1(int number1)
   {
      //implementation
   }

   //i dont want this method in this class
   public override int Method2(int number2)
   {
        throw new NotImplementedException();
   }
}

public class Class2 : SomethingBase
{

   //i dont want this method in this class
   public override int Method1(int number1)
   {
     throw new NotImplementedException();
   }

   public override int Method2(int number2)
   {
    //implementation
    }
}

Я имею в виду ситуацию, если мне нужен method1 в моем Class1, а method2 нет, и vica verse для Class2. Фактически методы исключают друг друга в производных классах.

Ответы [ 8 ]

9 голосов
/ 23 декабря 2009

Это неприемлемый дизайн в его нынешнем виде. Чтобы сделать его более приемлемым, вы можете реализовать два интерфейса в абстрактном классе. Один для Method1() и один для Method2(), и просто используйте объект интерфейса для его передачи.

Вы действительно не хотите использовать определения Class1 и Class2, так как существует опасность того, что ваши клиенты вызовут метод, который не реализован. Поэтому я бы предложил использовать интерфейсы и использовать их вместо этого.

4 голосов
/ 23 декабря 2009

Это ломается Принцип замещения Лискова (a.k.a. LSP ):

Пусть q (x) - свойство, доказуемое для объектов x типа T. Тогда q (y) должно быть истинным для объектов y типа S, где S - это подтип T.

Я также считаю это правилом IS-A . Если класс B является производным от класса A, он должен поддерживать все те же функциональные возможности, что и класс A, и не нарушать его поведение в процессе. Если что-то потребляет объект класса A, вы должны иметь возможность передать ему объект класса B, а не разбить его.

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

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

4 голосов
/ 23 декабря 2009

Я думаю, что с вашим дизайном что-то не так. Два производных объекта должны быть производными только от базового класса, который действительно представляет общность между ними. В вашем примере базовый класс не представляет общности между Class1 и Class2.

3 голосов
/ 23 декабря 2009

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

Но иногда это трудно избежать или предсказать.

Классический пример в .NET, где используется упомянутый вами подход - Stream класс, где некоторые дочерние элементы выдают NotSupportedException для некоторых абстрактных свойств и методов. (например, Position свойство в DeflateStream классе.) Но для правильной реализации этого проекта у вас должны быть либо виртуальные свойства CanMethod1, либо методы Method1Supported(). (Как в TypeConverter - обычно, когда у вас есть более сложные случаи, в основном, когда метод поддерживается только в некоторых контекстах.)

3 голосов
/ 23 декабря 2009

Это неприемлемый дизайн, поскольку он нарушает контракт базового класса. Возьмите, например, следующий код клиента

public void SomeFunction(SomethingBase x){
     x. Method2(1); // This will fail for Class1 but will pass for Class2. This is faulty.
}

Вместо этого вы можете кодировать интерфейс и иметь один интерфейс, содержащий Method1, а второй интерфейс - Method2.

interface IM1{
     int Method1(int number1);
}

interface IM2{
     int Method2(int number2);
}

class Class1 : IM1{
     int Method1(int number1){
          // Implement your logic
     }
}

class Class2 : IM2{
     int Method2(int number2){
          // Implement your logic
     }
}

// Client Code
    public void SomeFunction(IM2 x){
         x. Method2(1); // This will work as this is the only thing this client wants.
    }
3 голосов
/ 23 декабря 2009

Это приемлемый дизайн ??

Нет.Я могу быть приемлемым в некоторых ситуациях (например, ReadOnlyCollection в FCL), но вы должны избегать его, где это возможно.

Вы можете использовать два интерфейса: один с Method1 и один с Method2 .

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

2 голосов
/ 23 декабря 2009

Прежде всего, если вы не определяете какие-либо переменные-члены в абстрактном классе, я бы, вероятно, просто использовал интерфейсы.

Во-вторых, я бы, вероятно, просто имел один метод в абстрактном классе, и каждый из классов реализовывал бы его по-своему (со своей собственной специальной логикой), поскольку сигнатуры Method1 и Method2 кажутся одинаковыми.

1 голос
/ 23 декабря 2009

Не согласен с большинством, а регент проголосовал. Хочу поддержать и расширить свою позицию.

НЕ ВСЕГДА плохой дизайн для использования NotSupportedException. Вы должны взвесить все за и против.

Что если у вас большое разнообразие классов с почти, но не одинаковыми наборами методов? Потоки являются хорошим примером. Если вы попытаетесь отсортировать все эти методы в интерфейсах, это 1) даст вам дополнительную работу по изобретению интуитивно понятных и кратких имен для всех этих интерфейсов, 2) заставит пользователя интерфейсов понять, что делает каждый из них.

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

...