Абстрактный базовый класс против конкретного класса как супертип - PullRequest
12 голосов
/ 28 июня 2010

После прочтения самой превосходной книги «Образцы дизайна для головы» я начал распространять среди своих коллег преимущества шаблонов и принципов проектирования. Во время восхваления достоинств моего любимого паттерна - паттерна стратегии - мне задали вопрос, который заставил меня задуматься. Стратегия, конечно, использует наследование и композицию, и я был на одной из моих тирад о «программировании интерфейса (или супертипа), а не реализации», когда коллега спросил «зачем использовать абстрактный базовый класс вместо конкретного класса?» .
Я мог бы только придумать «хорошо, что вы заставляете свои подклассы реализовывать абстрактные методы и не позволяете им создавать экземпляр ABC». Но, честно говоря, вопрос застал меня врасплох. Являются ли это единственными преимуществами использования абстрактного базового класса над конкретным классом на вершине моей иерархии?

Ответы [ 8 ]

22 голосов
/ 28 июня 2010

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

5 голосов
/ 28 июня 2010

Программа для интерфейса, а не для реализации имеет мало общего с абстрактными и конкретными классами. Помните шаблон метода шаблона ? Классы, абстрактные или конкретные, являются деталями реализации.

И причина использовать абстрактные классы вместо конкретных классов в том, что вы можете вызывать методы, не реализуя их, а оставляя их для реализации подклассами.

Программирование для интерфейса - это совсем другое - оно определяет что делает ваш API, а не как это делает. И это обозначено интерфейсами.

Обратите внимание на одно ключевое отличие - у вас могут быть методы protected abstract, что означает, что это детали реализации. Но все методы интерфейса общедоступны - часть API.

1 голос
/ 29 июня 2010

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

public abstract class Animal{

public void digest(){

}

public abstract void sound(){

}
}

public class Dog extends Animal{
public void sound(){
    System.out.println("bark");
}
}

Паттерн Stratergy просит дизайнеров использовать композиционное поведение для случаев, когда для поведения существуют семейства алогиртов.

1 голос
/ 28 июня 2010

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

Что касается абстрактного базового класса или конкретного класса, то ответ в Java всегда звучит так: «Что работает лучше в этой ситуации?»Если ваши стратегии могут делиться кодом, то непременно дайте им это сделать.

Шаблоны проектирования не являются инструкциями о том, как что-то сделать.Это способы классификации вещей, которые мы уже сделали.

1 голос
/ 28 июня 2010

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

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

0 голосов
/ 03 июня 2016

Предпочитают абстрактные базовые классы в следующих сценариях:

  1. Базовый класс не может существовать без подкласса => базовый класс просто абстрактный и его нельзя создать.
  2. Базовый класс не может иметь полную или конкретную реализацию метода => Реализация метода в том случае, если базовый класс неполон, и только подклассы могут обеспечить полную реализацию.
  3. Базовый класс предоставляет шаблон для реализации метода, но он все еще зависит от конкретного класса, чтобы завершить реализацию метода - Template_method_pattern

Простой пример, иллюстрирующий вышеприведенные пункты

Shape является абстрактным и не может существовать без конкретной формы, такой как Rectangle. Рисование Shape не может быть реализовано в классе Shape, поскольку разные формы имеют разные формулы. Лучший вариант для обработки сценария: оставить реализацию draw() для подклассов

abstract class Shape{
    int x;
    int y;
    public Shape(int x,int y){
        this.x = x;
        this.y = y;
    }
    public abstract void draw();
}
class Rectangle extends Shape{
    public Rectangle(int x,int y){
        super(x,y);
    }
    public void draw(){
        //Draw Rectangle using x and y : length * width
        System.out.println("draw Rectangle with area:"+ (x * y));
    }
}
class Triangle extends Shape{
    public Triangle(int x,int y){
        super(x,y);
    }
    public void draw(){
        //Draw Triangle using x and y : base * height /2
        System.out.println("draw Triangle with area:"+ (x * y) / 2);
    }
}
class Circle extends Shape{
    public Circle(int x,int y){
        super(x,y);
    }
    public void draw(){
        //Draw Circle using x as radius ( PI * radius * radius
        System.out.println("draw Circle with area:"+ ( 3.14 * x * x ));
    }
}

public class AbstractBaseClass{
    public static void main(String args[]){
        Shape s = new Rectangle(5,10);
        s.draw();
        s = new Circle(5,10);
        s.draw();
        s = new Triangle(5,10);
        s.draw();
    }
}

выход:

draw Rectangle with area:50
draw Circle with area:78.5
draw Triangle with area:25

Выше код охватывает точку 1 и точку 2. Вы можете изменить метод draw() как шаблонный метод, если базовый класс имеет некоторую реализацию и вызывает метод подкласса для завершения draw() функции.

Теперь тот же пример с шаблоном метода Template:

abstract class Shape{
    int x;
    int y;
    public Shape(int x,int y){
        this.x = x;
        this.y = y;
    }
    public abstract void draw();

    // drawShape is template method
    public void drawShape(){
        System.out.println("Drawing shape from Base class begins");
        draw();
        System.out.println("Drawing shape from Base class ends");       
    }
}
class Rectangle extends Shape{
    public Rectangle(int x,int y){
        super(x,y);
    }
    public void draw(){
        //Draw Rectangle using x and y : length * width
        System.out.println("draw Rectangle with area:"+ (x * y));
    }
}
class Triangle extends Shape{
    public Triangle(int x,int y){
        super(x,y);
    }
    public void draw(){
        //Draw Triangle using x and y : base * height /2
        System.out.println("draw Triangle with area:"+ (x * y) / 2);
    }
}
class Circle extends Shape{
    public Circle(int x,int y){
        super(x,y);
    }
    public void draw(){
        //Draw Circle using x as radius ( PI * radius * radius
        System.out.println("draw Circle with area:"+ ( 3.14 * x * x ));
    }
}

public class AbstractBaseClass{
    public static void main(String args[]){
        Shape s = new Rectangle(5,10);
        s.drawShape();
        s = new Circle(5,10);
        s.drawShape();
        s = new Triangle(5,10);
        s.drawShape();
    }
}

выход:

Drawing shape from Base class begins
draw Rectangle with area:50
Drawing shape from Base class ends
Drawing shape from Base class begins
draw Circle with area:78.5
Drawing shape from Base class ends
Drawing shape from Base class begins
draw Triangle with area:25
Drawing shape from Base class ends

Как только вы решили, что вы должны сделать как метод abstract, у вас есть два варианта: либо пользовательский interface, либо abstract класс. Вы можете объявить ваши методы в interface и определить abstract класс как класс, реализующий interface.

0 голосов
/ 28 июня 2010

Вопрос о том, должен ли базовый класс быть абстрактным или конкретным, во многом зависит от IMHO, будет ли полезен объект базового класса, в котором реализованы только те поведения, которые являются общими для всех объектов класса. Рассмотрим WaitHandle. Вызов «wait» для него приведет к блокировке кода до тех пор, пока не будет выполнено какое-либо условие, но нет общего способа сообщить объекту WaitHandle, что его условие выполнено. Если бы можно было создать экземпляр «WaitHandle», в отличие от возможности создавать экземпляры экземпляров производных типов, такой объект должен был бы либо никогда не ждать, либо всегда ждать вечно. Последнее поведение было бы довольно бесполезным; первый может быть полезен, но может быть достигнут почти также с помощью статически распределенного ManualResetEvent (я думаю, что последний тратит несколько ресурсов, но если он статически распределен, общая потеря ресурсов должна быть тривиальной).

Во многих случаях я думаю, что я предпочел бы использовать ссылки на интерфейс, а не на абстрактный базовый класс, но обеспечить интерфейс базовым классом, который обеспечивает «реализацию модели». Таким образом, в любом месте, где можно использовать ссылку на MyThing, можно указать ссылку на «iMyThing». Вполне может быть, что 99% (или даже 100%) объектов iMyThing на самом деле являются MyThing, но если кому-то когда-либо понадобится объект iMyThing, который наследуется от чего-то другого, это можно сделать.

0 голосов
/ 28 июня 2010

Если клиент полагается на «подразумеваемый контракт [ы] поведения», он запрограммирован на реализацию и на негарантированное поведение.Переопределение метода при выполнении контракта только выявляет ошибки в клиенте, а не вызывает их.

OTOH, ошибка принятия контрактов, которых там нет, с меньшей вероятностью вызовет проблемы, если рассматриваемый метод не является виртуальным - то есть, переопределение его не может вызвать проблемы, потому что его нельзя переопределить.Только если реализация оригинального метода будет изменена (при условии соблюдения контракта), он может сломать клиента.

...