Объектно-ориентированный дизайн - самый простой случай, но я все равно запутался! - PullRequest
4 голосов
/ 20 августа 2010

Когда я оборачиваю некоторый процедурный код в классе (в моем случае c ++, но это, вероятно, здесь не представляет интереса), я часто путаюсь с лучшим способом сделать это. С процедурным кодом я имею в виду то, что вы могли бы легко вставить в процедуру и где вы используете окружающий объект в основном для ясности и простоты использования (обработка ошибок, ведение журнала, обработка транзакций ...).

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

Как эти данные попадают в объект best . Давайте предположим, что ему нужно 7 значений и список целых чисел.

Мои идеи:

  • Список параметров конструктора
  • Установить функции
  • Список параметров центральной функции

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

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

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

Как ты делаешь что-то подобное? Правильны ли мои соображения? Мне не хватает некоторых достоинств / недостатков?

Этот материал настолько прост, но я не смог найти на нем никаких ресурсов.

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

Давайте сделаем опрос, что вам больше нравится:

class WriteAdress {
  WriteAdress(string name, string street, string city);
  void Execute();
}

или

class WriteAdress {
  void Execute(string name, string street, string city);
}

или

class WriteAdress {
  void SetName(string Name);
  void SetStreet(string Street);
  void SetCity(string City);
  void Execute();
}

или

class WriteAdress {
  void SetData(string name, string street, string city);
  void Execute();
}

Ответы [ 8 ]

5 голосов
/ 20 августа 2010

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

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

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

4 голосов
/ 20 августа 2010

Дизайн интерфейса очень важен, но в вашем случае вам нужно узнать, что худшее - лучше .

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

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

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

0 голосов
/ 02 сентября 2014

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

0 голосов
/ 20 августа 2010

Я предполагаю, что единственная ответственность вашего WriteAddress класса - записать адрес в базу данных или в выходной поток. Если это так, то вам не следует беспокоиться о методах получения и установки адресов; вместо этого определите интерфейс AddressDataProvider, который должен быть реализован всеми классами, с которыми будет работать ваш класс WriteAddress.

Одним из методов в этом интерфейсе будет GetAddressParts(), который будет возвращать массив строк в соответствии с WriteAddress. Любой класс, который реализует этот метод, должен будет соблюдать эту структуру массива.

Затем в WriteAddress определите установщик SetDataProvider(AddressDataProvider). Этот метод будет вызываться кодом, который создает экземпляры ваших WriteAddress объектов.

Наконец, в вашем методе Execute() получите необходимые данные, позвонив по номеру GetAddressParts() «провайдеру данных», который вы установили, и запишите свой адрес.

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

Это очевидно даже в описании этого дизайна: только два имени WriteAddress и AddressDataProvider подходят; там нет упоминания о базе данных или о том, как передать детали адреса. Это обычно является признаком высокой когезии и низкого сцепления.

Надеюсь, это поможет.

0 голосов
/ 20 августа 2010

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

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

0 голосов
/ 20 августа 2010

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

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

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

0 голосов
/ 20 августа 2010

Во-первых, звучит так, будто вы пытаетесь сделать слишком много одновременно.Чтение, вычисление и обновление - все это отдельные операции, которые сами, вероятно, могут быть разделены далее.

Техника, которую я использую, когда я думаю о разработке метода или класса, заключается в том, чтобы подумать: «Как я хочу, чтобы метод высшего уровня идеально выглядел?»т.е. подумайте об отдельных компонентах метода и разбейте их на части.Это нисходящий дизайн.

В вашем случае я предусмотрел это в своей голове (C #):

public static void Dostuff(...)
{
    Data d = ReadDatabase(...);
    d.DoCalculations(...);
    UpdateDatabase(d);
}

Затем сделайте то же самое для каждого из этих методов.

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

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

В любом случае, обычно и не считается плохой практикой иметь как конструктор, так и несколько get /установить функции там, где это необходимо.Также обычно приходится проверять состояние объекта в начале метода, поэтому я не буду беспокоиться об этом.

Как вы можете видеть, это во многом зависит от того, что еще вы делаете в этом классе.

0 голосов
/ 20 августа 2010

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

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

Центральные функции должны использовать только временные параметры (временные метки, ..)

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