Стиль программирования OO - виртуальное свойство, параметр конструктора или защищенный установщик - PullRequest
3 голосов
/ 26 ноября 2010

Если у меня есть базовый класс (MyBase в приведенном ниже примере), которому нужны подклассы для предоставления / переопределения значения (Value), есть ли предпочтительный способ реализации этого? Я могу придумать несколько способов сделать это, но не очень понимаю, почему я бы выбрал один из них. У меня есть несколько примеров (в C #, но это может быть любой язык OO).

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

public class MyBase
{
    protected string Value { get; set; }

    protected MyBase()
    {
        Value = "Default Value";
    }

    private DoSomethingWithValue()
    {
         Console.WriteLine(Value);
    }
}

public class MySub : MyBase
{
    public MySub()
    {
        Value = "Overridden Value";
    }
}

Виртуальное свойство - Подклассы могут переопределить это, если захотят.

public class MyBase
{
    protected virtual string Value { get { return "Default Value"; } }

    protected MyBase()
    {
    }

    private DoSomethingWithValue()
    {
         Console.WriteLine(Value);
    }
}

public class MySub : MyBase
{
    protected override string Value { get { return "Overridden Value"; } }

    public MySub()
    {
    }
}

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

public class MyBase
{
    protected string Value { get; private set; }

    protected MyBase(string value)
    {
         Value = value;
    }

    protected MyBase() : this("Default Value")
    {
    }

    private DoSomethingWithValue()
    {
         Console.WriteLine(Value);
    }
}

public class MySub : MyBase
{
    public MySub() : base("Overridden Value")
    {
    }
}

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

Ответы [ 2 ]

0 голосов
/ 27 ноября 2010

Не обращайте внимания на все ошибки OO и начните с простой структуры с открытыми членами данных.Это наименьшая работа, самая простая для понимания, самая простая в использовании и самая мощная.Когда вы лучше поймете контекст использования и уверены, что класс действительно представляет абстракцию, тогда примените консервативные ограничения и внимательно наблюдайте, где ваш код ломается (если есть).

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

Например (в C ++, извините):

class Rational {
  int numerator; int denominator;
public: 
  Rational (int x, int y) { 
    if (y == 0) { throw DivisionByZero; }
    assert (y != 0);
    if (y<0) { x = -x; y = -y; }
    assert (y>0);
    int d = gcd(x,y);
    assert(d>0); // proof, from specs of gcd
    // assert: if there exists an integer q>0 which divides both x/d and y/d
    // then q == 1 (from definition of gcd)

    assert (x % d == 0); // d divides x exactly (from defn of gcd)
    assert (y % d == 0); // d divides y exactly (from defn of gcd)

    numerator = x/d;
    denominator = y/d;

    // assert: provided y != 0, numerator / denominator (math div, not C div)
    // equals input x/ input y (math div, not C div).

    // invariant: denominator > 0, gcd (numerator, denominator) == 1
    // Theorem: representation is unique
  }

  bool eq(Rational other) { 
    numerator == other.numerator && denominator == other.denominator;
    // sufficient by uniqueness theorem
  }
}:

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

В этом случае представление представляет собой стандартную каноническую форму следующего типа:

R: int * int - { x,y | y<0 or Exists d. d divides y and d divides x }

Это типичното, что представление состоит из декартового произведения минус подмножество, чтобы произвести множество R такое, что

R <==> A

, где A - абстрактный тип, является биекцией.

Моя точка зрения: не так просто правильно получить абстракцию.Программисты злоупотребляют сильной абстракцией, создавая такие устройства, как классы, особенно в ОО-языках, когда они не подозревают, что, например, простая структура, представляющая собой декартово произведение, уже формально абстрактна (представлена ​​функциями, в данном случае проекциями).

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

Правило Иттрила: если вы думаете об абстракции .. не надо!

Вам нужны доказательства, а не мысли!

0 голосов
/ 26 ноября 2010

Если значение известно во время компиляции для всех потенциальных производных классов, почему вы храните его во время выполнения?

Что еще важнее, вызывать виртуальный поиск здесь, когда вы знаете, абсолютно бесполезночто они все просто return "constant";, поэтому я бы пошел на конкретное свойство в базе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...