2010-06-28 3 views
12

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

+0

По-моему ДА. Но я считаю, что это очень важная «особенность» языка, чтобы заставить кого-то, кто наследует этот класс, внедрить метод. Иногда вам нужно создать общий класс, но не может реализовать все функции, потому что это будет слишком специфично. – KroaX

ответ

22

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

+0

+1. Отличный контроль над тем, что использовать и когда. – NotMe

+1

А? Почему метод переопределяет запах? – ladenedge

+5

Код с виртуальными методами намного сложнее изменить в будущем, не нарушая какой-либо подразумеваемый поведенческий контракт. Таким образом, в .NET они решили, что сделать метод overridable должен быть сознательным решением и должен быть тщательно рассмотрен. Поэтому любой переопределяемый метод без инструкций для разработчика является «запахом кода». –

1

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

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

5

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

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

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

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

+0

Чтобы добавить к этому, «Программа для реализации» будет делать такие вещи, как использование refelection для доступа к закрытым полям класса. –

1

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

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

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

0

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

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

0

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

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

1

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

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 для поведения.

0

предпочитают абстрактные базовые классы ниже сценариев:

  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().

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

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.

Смежные вопросы