Подтвердить что ты не робот

Абстрактный базовый класс против класса бетона в виде супертипа

После прочтения самой прекрасной книги "Head First Design Patterns" я начал прозелитизировать моим коллегам преимущества шаблонов и принципов дизайна. Превознося достоинства моей любимой картины - Strategy Pattern - мне задали вопрос, который дал мне паузу. Стратегия, конечно, использует наследование и состав, и я был на одном из своих тирадов о "программе для интерфейса (или супертипа), а не реализации", когда коллега спросил "зачем использовать абстрактный базовый класс вместо конкретного класса?".
Я мог только придумать "хорошо, что вы заставляете свои подклассы реализовывать абстрактные методы и препятствовать им создавать экземпляр ABC". Но, честно говоря, этот вопрос заставил меня покинуть гаурд. Являются ли они единственными преимуществами использования абстрактного базового класса над конкретным классом в верхней части моей иерархии?

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

public abstract class Animal{

public void digest(){

}

public abstract void sound(){

}
}

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

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

  • Базовый класс не может существовать без подкласса = > базовый класс просто абстрактен и не может быть создан.
  • Базовый класс не может иметь полную или конкретную реализацию метода = > Реализация метода является базовым классом неполным, и только подклассы могут обеспечить полную реализацию.
  • Базовый класс предоставляет шаблон для реализации метода, но по-прежнему зависит от класса Concrete для завершения реализации метода - 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().

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

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.