Как вы избегаете добытчиков и сеттеров?


85

У меня были некоторые трудности с дизайном классов. Я читал, что объекты раскрывают их поведение, а не данные; следовательно, вместо использования методов получения / установки для изменения данных, методы данного класса должны быть «глаголами» или действиями, действующими на объект. Например, в объекте «Счета», мы имели бы методы Withdraw()и Deposit()вместо того , и setAmount()т.д. См: Почему методы получения и установки являются злом .

Так, например, учитывая класс Customer, который хранит много информации о клиенте, например, имя, DOB, телефон, адрес и т. Д., Как можно избежать получения / установки всех этих атрибутов? Какой метод типа «Поведение» можно написать, чтобы заполнить все эти данные?




8
Я хотел бы отметить, что если вам придется иметь дело со спецификацией Java Beans , у вас будут геттеры и сеттеры. Многие вещи используют Java Beans (язык выражений в jsps), и попытка избежать этого, вероятно, будет ... сложной.

4
... и с противоположной точки зрения от MichaelT: если вы не используете спецификацию JavaBeans (и я лично считаю, что вам следует избегать этого, если вы не используете свой объект в контексте, где это необходимо), тогда нет необходимости "get" бородавка на геттерах, особенно для свойств, у которых нет соответствующего установщика. Я думаю , что метод , названный name()на Customerэто ясно, или яснее, чем метод , называемый getName().
Даниэль Приден

2
@Daniel Pryden: name () может означать как установить, так и получить? ...
IntelliData

Ответы:


55

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

В качестве примера представьте, что ваш проект представляет собой настольную игру, такую ​​как Chess или Battleship. У вас могут быть различные способы представления этого на уровне представления (консольное приложение, веб-служба, графический интерфейс и т. Д.), Но у вас также есть основной домен. Один класс, который вы можете иметь Coordinate, представляет позицию на доске. «Злой» способ написать это будет:

public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

(Я собираюсь писать примеры кода на C #, а не на Java, для краткости и потому, что я более знаком с этим. Надеюсь, это не проблема. Концепции одинаковы, и перевод должен быть простым.)

Удаление сеттеров: неизменность

В то время как публичные геттеры и сеттеры являются потенциально проблематичными, сеттеры являются гораздо более «злым» из двух. Их также обычно легче устранить. Процесс прост - установить значение из конструктора. Любые методы, которые ранее мутировали объект, должны вместо этого возвращать новый результат. Так:

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}

    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
}

Обратите внимание, что это не защищает от других методов в классе, изменяющих X и Y. Чтобы быть более неизменными, вы можете использовать readonly( finalв Java). Но так или иначе - независимо от того, делаете ли вы свои свойства по-настоящему неизменными или просто предотвращаете прямую публичную мутацию через сеттеры - это помогает избавиться от ваших публичных сеттеров. В подавляющем большинстве ситуаций это работает просто отлично.

Извлечение добытчиков, часть 1: проектирование поведения

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

Итак, скажем, первое место, в котором вы нуждались, Coordinateбыло обнаружение столкновений: вы хотели проверить, занимают ли две фигуры одинаковое пространство на доске. Вот «злой» путь (конструкторы для краткости опущены):

public class Piece
{
    public Coordinate Position {get; private set;}
}

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}
}

    //...And then, inside some class
    public bool DoPiecesCollide(Piece one, Piece two)
    {
        return one.X == two.X && one.Y == two.Y;
    }

И вот хороший способ:

public class Piece
{
    private Coordinate _position;
    public bool CollidesWith(Piece other)
    {
        return _position.Equals(other._position);
    }
}

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;
    public bool Equals(Coordinate other)
    {
        return _x == other._x && _y == other._y;
    }
}

( IEquatableреализация сокращенно для простоты). Разрабатывая поведение, а не моделируя данные, нам удалось удалить наших получателей.

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

Удаление добытчиков, часть 2: внешнее поведение

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

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

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

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

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

Для нашей проблемы, решение с использованием этого шаблона будет:

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;

    public T Transform<T>(IPositionTransformer<T> transformer)
    {
        return transformer.Transform(_x,_y);
    }
}

public interface IPositionTransformer<T>
{
    T Transform(int x, int y);
}

//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
    private readonly float _tileWidth;
    private readonly float _tileHeight;
    private readonly Vector2 _topLeft;

    Vector2 Transform(int x, int y)
    {
        return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
    }
}

Как вы можете сказать, _xи _yне являются действительно инкапсулированные больше. Мы могли бы извлечь их, создавая, IPositionTransformer<Tuple<int,int>>который просто возвращает их напрямую. В зависимости от вкуса, вы можете чувствовать, что это делает все упражнение бессмысленным.

Тем не менее, с публичными получателями очень легко сделать что-то не так, просто извлекая данные напрямую и используя их в нарушение « Скажите, не спрашивайте» . В то время как с помощью этого шаблона на самом деле проще сделать его правильно: когда вы хотите создать поведение, вы автоматически начнете с создания типа, связанного с ним. Нарушения TDA будут очень вонючими и, вероятно, потребуют решения более простого, лучшего решения. На практике эти пункты значительно облегчают правильное выполнение, ОО, способом, чем «злой», который поощряют добывающие.

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

public class CoordinateToVectorTransformer
{
    private Dictionary<Coordinate,Vector2> _coordinatePositions;

    public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
    {
        for(int x=0; x<boardWidth; x++)
        {
            for(int y=0; y<boardWidth; y++)
            {
                _coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
            }
        }
    }

    private static Vector2 GetPosition(int x, int y)
    {
        //Some implementation goes here...
    }

    public Vector2 Transform(Coordinate coordinate)
    {
        return _coordinatePositions[coordinate];
    }
}

Оказывается, что удивительно, оказывается, что все поведение, которое нам действительно требовалось от координаты для достижения нашей цели, было проверкой на равенство! Конечно, это решение адаптировано к этой проблеме и делает предположения о приемлемом использовании / производительности памяти. Это всего лишь пример, который подходит для этой конкретной предметной области, а не план для общего решения.

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


Красиво ответил! Я хотел бы принять, но сначала некоторые комментарии: 1. Я действительно считаю, что toDTO () - это здорово, потому что вы не можете получить доступ к get / set, что позволяет вам изменять поля, переданные DTO, без нарушения существующего кода. 2. Скажите, что у Клиента достаточно поведения, чтобы оправдать превращение его в
единое

@IntelliData 1. Когда вы говорите «изменить поля», вы имеете в виду изменить определение класса или изменить данные? Последнего можно просто избежать, удалив публичные установщики, но оставив получатели, поэтому аспект dto не имеет значения. Первое не является (целой) причиной того, что публичные добытчики являются «злыми». См programmers.stackexchange.com/questions/157526/... , например.
Бен Ааронсон

@IntelliData 2. Трудно ответить, не зная поведения. Но, вероятно, ответ таков: вы бы этого не сделали. Какое поведение может иметь Customerкласс, требующий изменения своего телефонного номера? Возможно, телефонный номер клиента меняется, и мне нужно сохранить это изменение в базе данных, но ни одно из этих действий не является обязанностью объекта домена, обеспечивающего поведение. Это проблема доступа к данным, и, вероятно, она будет решена с помощью DTO и, скажем, хранилища.
Бен Ааронсон

@IntelliData Сохранение Customerданных объекта домена относительно свежим (синхронно с базой данных) - это вопрос управления его жизненным циклом, который также не является его собственной ответственностью, и, вероятно, он снова окажется в хранилище, на фабрике или в контейнере IOC или что бы ни создавало Customerс.
Бен Ааронсон

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

71

Самый простой способ избежать установки - это передать значения в метод конструктора, когда вы newподнимаете объект. Это также обычный шаблон, когда вы хотите сделать объект неизменным. Тем не менее, вещи не всегда так ясны в реальном мире.

Это правда, что методы должны быть о поведении. Однако некоторые объекты, такие как Customer, существуют в первую очередь для хранения информации. Это те виды объектов, которые больше всего выигрывают от геттеров и сеттеров; если бы в таких методах не было нужды, мы бы просто полностью их ликвидировали.

Дальнейшее чтение
Когда оправданы геттеры и сеттеры


3
так почему все шумихи по поводу «добытчиков / сеттеров» злые?
IntelliData

46
Просто обычное понтификация разработчиками программного обеспечения, которые должны знать лучше. Для статьи, на которую вы ссылаетесь, автор использует фразу «геттеры и сеттеры - зло», чтобы привлечь ваше внимание, но это не означает, что утверждение категорически верно.
Роберт Харви

12
@IntelliData, некоторые люди скажут вам, что сама Java - это зло.
ноль

18
@MetaFightsetEvil(null);

4
Конечно, Eval - это воплощенное зло. Или близкий кузен.
епископ

58

Прекрасно иметь объект, который предоставляет данные, а не поведение. Мы просто называем это «объект данных». Шаблон существует под такими именами, как Data Transfer Object или Value Object. Если целью объекта является хранение данных, то методы получения и установки действительны для доступа к данным.

Так почему кто-то сказал бы, что методы получения и установки - это зло? Вы увидите это очень часто - кто-то возьмет руководство, которое совершенно правильно в определенном контексте, а затем удалит контекст, чтобы получить более жесткий заголовок. Например, « композиция благосклонности перед наследованием » является хорошим принципом, но довольно скоро кто-то собирается удалить контекст и написать « Почему расширяется зло » (эй, тот же автор, какое совпадение!) Или « наследование зло и должно быть уничтожен ".

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

Прочитайте продолжение статьи, на которую вы ссылаетесь, чтобы увидеть, как он предлагает на самом деле устанавливать свойства, такие как «имя» и «зарплата», для объекта «Сотрудник» без использования злых сеттеров. Оказывается, он использует шаблон с 'Exporter', который заполняется методами, называемыми add Name, add Salary, который, в свою очередь, устанавливает поля с одинаковыми именами ... Так что, в конце концов, он заканчивает тем, что использует именно шаблон сеттера, просто с другое соглашение об именах.

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


Похоже, что так называемые эксперты говорят, что это не так.
IntelliData

8
С другой стороны, некоторые люди говорят: «Никогда не используйте ООП»: вредный.кат-v.org
software/

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

4
@leftaroundabout: ОК, но я предлагаю золотую середину между «всегда» и «никогда», что означает «использовать при необходимости».
JacquesB

3
Я думаю, проблема в том, что многие программисты превращают каждый объект (или слишком много объектов) в DTO. Иногда они необходимы, но по возможности избегайте их, поскольку они отделяют данные от поведения. (Предполагая, что вы являетесь преданным ООПером)
user949300

11

Чтобы преобразовать Customer-класс из объекта данных, мы можем задать себе следующие вопросы о полях данных:

Как мы хотим использовать {поле данных}? Где используется {поле данных}? Можно и нужно ли использовать {поле данных} в классе?

Например:

  • Какова цель Customer.Name?

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

    Что приводит к методам:

    • Customer.FillInTemplate (...)
    • Customer.IsApplicableForMailing (...)
  • Какова цель Customer.DOB?

    Проверка возраста клиента. Скидки на день рождения клиента. Mailings.

    • Customer.IsApplicableForProduct ()
    • Customer.GetPersonalDiscount ()
    • Customer.IsApplicableForMailing ()

С учетом комментариев пример объекта Customer- как объекта данных, так и «реального» объекта со своими обязанностями - слишком широк; то есть у него слишком много свойств / обязанностей. Что приводит либо к большому количеству компонентов в зависимости от Customer(путем чтения его свойств), либо к Customerзависимости от множества компонентов. Возможно, существуют разные взгляды клиента, возможно, у каждого должен быть свой отдельный класс 1 :

  • Клиент в контексте Accountи денежных транзакций, вероятно, используется только для:

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

    Этот клиент не нуждается в области , как DOB, FavouriteColour, Tel, и , возможно , даже не Address.

  • Клиент в контексте входа пользователя на банковский сайт.

    Соответствующие поля:

    • FavouriteColourкоторый может прийти в форме персонализированной темы;
    • LanguagePreferences, а также
    • GreetingName

    Вместо свойств с геттерами и сеттерами они могут быть записаны одним методом:

    • PersonaliseWebPage (страница шаблона);
  • Заказчик в контексте маркетинга и персонализированной рассылки.

    Здесь не полагаться на свойства объекта данных, а вместо этого, начиная с ответственности объекта; например:

    • IsCustomerInterestedInAction (); а также
    • GetPersonalDiscounts ().

    Тот факт, что этот объект клиента имеет FavouriteColourсвойство и / или Addressсвойство, становится неактуальным: возможно, реализация использует эти свойства; но он также может использовать некоторые методы машинного обучения и использовать предыдущие взаимодействия с клиентом, чтобы выяснить, какие продукты могут заинтересовать клиента.


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


4
Подтвердите, потому что вы на самом деле отвечаете на вопрос :) Однако также очевидно, что предлагаемые решения намного хуже, чем просто наличие gettes / setters - например, FillInTemplate явно нарушает принцип разделения интересов. Что говорит о том, что предпосылка вопроса ошибочна.
JacquesB

@Kasper van den Berg: А когда у вас много атрибутов в Customer, как это обычно бывает, как бы вы их изначально установили?
IntelliData

2
@IntelliData ваши значения, вероятно, поступают из базы данных, XML и т. Д. Customer или CustomerBuilder считывает их, а затем задает с помощью (т. Е. В Java) доступа к частному / пакетному / внутреннему классу или (ick) отражения. Не идеально, но обычно вы можете избежать публичных сеттеров. (Смотрите мой ответ для более подробной информации)
user949300

6
Я не думаю, что клиентский класс должен знать о любой из этих вещей.
CodesInChaos

Как насчет чего-то вроде Customer.FavoriteColor?
Гейб

8

TL; DR

  • Моделирование поведения - это хорошо.

  • Моделирование для хороших (!) Абстракций лучше.

  • Иногда требуются объекты данных.


Поведение и Абстракция

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

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

Даже пара deposit()и withdraw()методы не идеальны для моделирования банковского счета. Лучшим способом было бы предоставить только один transfer()метод, который принимает другой учет и количество в качестве аргументов. Это позволило бы классу учетной записи тривиально гарантировать, что вы случайно не создадите / уничтожите деньги в своей системе, это обеспечит очень удобную абстракцию и фактически предоставит пользователям больше понимания, потому что это заставит использовать специальные учетные записи для заработанные / вложенные / потерянные деньги (см. двойной учет ). Конечно, не каждому использованию учетной записи необходим такой уровень абстракции, но определенно стоит подумать, сколько абстракции могут обеспечить ваши классы.

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


Класс клиента

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

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

Тем не менее, установщики могут / должны предоставлять нетривиальные услуги: они могут обеспечить правильный формат адресов электронной почты, проверку почтовых адресов и т. Д. Аналогично, «получатели» могут предоставлять услуги высокого уровня, такие как предоставление адресов электронной почты в Name <user@server.com>формате использование полей имени и депонированного адреса электронной почты или предоставление правильно отформатированного почтового адреса и т. д. Конечно, то, что из этой функциональности высокого уровня имеет смысл, сильно зависит от вашего варианта использования. Это может быть полным излишним, или может потребоваться, чтобы другой класс сделал это правильно. Выбор уровня абстракции не из легких.


звучит правильно, хотя я не согласен с сексом ...;)
IntelliData

6

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

Когда изменится Customer.Name?

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

Когда изменится DOB?

Только при первоначальном создании или при вводе данных. Или если они бейсболист Domincan. :-)

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

Обычно Заказчик читается из базы данных, API REST, некоторого XML, что угодно. Иметь метод Customer.readFromDB()или, если вы более строго относитесь к SRP / разделению интересов, у вас будет отдельный компоновщик, например CustomerPersisterобъект с read()методом. Внутренне они как-то устанавливают поля (я предпочитаю использовать доступ к пакету или внутренний класс, YMMV). Но опять же, избегайте публичных сеттеров.

(Приложение как Вопрос несколько изменилось ...)

Допустим, ваше приложение интенсивно использует реляционные базы данных. Было бы глупо иметь Customer.saveToMYSQL()или Customer.readFromMYSQL()методы. Это создает нежелательную связь с конкретной, нестандартной и, вероятно, изменяющейся сущностью. Например, когда вы меняете схему или меняете ее на Postgress или Oracle.

Тем не менее, ИМО, это вполне приемлемо для пары Клиента к абстрактному стандарта , ResultSet. Отдельный вспомогательный объект (я назову его CustomerDBHelper, который, вероятно, является подклассом AbstractMySQLHelper) знает обо всех сложных соединениях с вашей БД, знает хитрые детали оптимизации, знает таблицы, запросы, объединения и т. Д. (Или использует ORM как Hibernate) для генерации ResultSet. Ваш объект говорит к ResultSet, который является абстрактным стандартом , вряд ли изменится. Когда вы изменяете базовую базу данных или меняете схему, Customer не меняется , а CustomerDBHelper . Если вам повезет, то изменяется только AbstractMySQLHelper, который автоматически вносит изменения для клиента, продавца, доставки и т. Д.

Таким образом, вы можете (возможно) избежать или уменьшить потребность в геттерах и сеттерах.

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

Точно так же, скажем, вы используете много XML. ИМО, хорошо связать вашего Заказчика с абстрактным стандартом, таким как Python xml.etree.ElementTree или Java org.w3c.dom.Element . Клиент получает и устанавливает себя от этого. Опять же, вы можете (возможно) уменьшить потребность в геттерах и сеттерах.


Вы бы сказали, что желательно использовать шаблон Builder?
IntelliData

Конструктор полезен для упрощения и повышения надежности построения объекта, а также, если вы хотите, чтобы объект был неизменным. Тем не менее, он все еще (частично) раскрывает тот факт, что нижележащий объект имеет поле DOB, так что это не пчеловодство в конце концов.
user949300

1

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

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

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

Ваш класс может иметь универсальный метод "fromMetadata" или "toMetadata". Метаданные from конструируют объект, поэтому вполне могут быть конструктором. Если это язык с динамической типизацией, метаданные довольно стандартны для такого языка и, вероятно, являются основным способом создания таких объектов.

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

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


0

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

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

Напротив, когда вы вызываете сетевую службу, вы предоставляете явный ввод. Там обычно грамматика (как в JSON или XML) и все возможности вызова службы не имеют причин быть скрытыми. Идея заключается в том, что вы можете называть службу так, как вам нужно, а формат данных является общедоступным и опубликованным.

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

Для такого требования к дизайну, когда вы хотите избежать инкапсуляции и обнародовать информацию, вы должны избегать объектов. Что вам действительно нужно, так это кортежи, структуры C или их эквиваленты, а не объекты.

Но это также происходит в таких языках, как Java, единственное, что вы можете моделировать, это объекты или массивы объектов. Объекты сами по себе могут содержать несколько нативных типов (int, float ...), но это все. Но объекты также могут вести себя как простая структура с простыми открытыми полями и всем этим.

Поэтому, если вы моделируете данные, вы можете сделать это только с помощью открытых полей внутри объектов, потому что вам не нужно больше. Вы не используете инкапсуляцию, потому что она вам не нужна. Это делается на многих языках. Исторически сложилось так, что в Java стандартная роза, где с помощью getter / setter вы могли бы по крайней мере иметь контроль чтения / записи (не добавляя, например, setter), и что инструментарий и инфраструктура, использующие API для инспектирования, будут искать методы getter / setter и использовать их автозаполнение содержимого или отображение тезисов в виде изменяемых полей в автоматически сгенерированном пользовательском интерфейсе.

Там также аргумент, который вы можете добавить немного логики / проверки в методе setter.

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

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

Скорее всего, вам придется изменить язык, чтобы использовать все эти шаблоны. (Как C # или lisp). Для меня геттеры / сеттеры - просто еще одна ошибка в один миллиард долларов ...


6
Свойства C # на самом деле не реализуют инкапсуляцию так же, как и
методы

1
Преимущество [gs] etters заключается в том, что вы можете делать то, что обычно не делаете (проверять значения, уведомлять наблюдателей), моделировать несуществующие поля и т. Д., И, главным образом, вы можете изменить это позже . С Ломбоком , шаблон отсутствует @Getter @Setter class MutablePoint3D {private int x, y, z;}.
Maaartinus

1
@maaartinus Конечно, [gs] etter может делать все что угодно, как любой другой метод. Это означает, что любой код вызывающей стороны должен знать, что любое значение, которое они устанавливают, может вызвать исключение, быть измененным или потенциально отправить уведомление об изменении ... или что-то еще. Более или менее [gs] etter не предоставляют доступ к полю, но делают произвольный код.
Николя Буске

Свойства @IntelliData C # позволяют не писать 1 бесполезный одиночный символ шаблонной таблицы и не заботиться обо всем этом, что является средством получения / установки ... Это уже лучшее достижение, чем проектный ломбок. Также для меня POJO с только getts / setter здесь не для обеспечения инкапсуляции, а для публикации формата данных, который можно свободно читать или записывать как обмен с сервисом. Инкапсуляция тогда является противоположностью проектного требования.
Николя Буске

Я не думаю, что свойства действительно хороши. Конечно, вы сохраняете префикс и скобки, то есть 5 символов на вызов, но 1. они выглядят как поля, что сбивает с толку. 2. это дополнительная вещь, для которой вам нужна поддержка в размышлениях. Ничего страшного, но и большого преимущества (по сравнению с Java + Lombok; чистая Java находится в явном проигрыше).
Maaartinus

0

Так, например, учитывая класс Customer, который сохраняет много информации о клиенте, например, Имя, DOB, Телефон, Адрес и т. Д., Как можно избежать получения / установки для получения и установки всех этих атрибутов? Какой метод типа «Поведение» можно написать, чтобы заполнить все эти данные?

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

Не путайте Customerкак класс объектов с «Заказчиком» как пользователем / субъектом, который выполняет различные задачи с использованием вашего программного обеспечения.

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

Из связанной статьи о том, что геттеры / сеттеры являются злыми:

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

Без какого-либо определенного поведения ссылка на камень как на элемент Customerне меняет того факта, что это просто объект с некоторыми свойствами, которые вы хотели бы отслеживать, и не имеет значения, какие приемы вы хотите использовать, чтобы уйти от добытчиков и сеттера. Скале не важно, имеет ли оно правильное имя, и скала не будет знать, действителен ли адрес или нет.

Ваша система заказов может связать a Rockс заказом на покупку, и, если для Rockнего определен адрес, некоторая часть системы может обеспечить доставку элемента в камень.

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


Попробуй это:

Когда вы избегаете перегрузки слова «Клиент» двумя потенциально разными значениями, это должно облегчить понимание.

Размещает ли Rockобъект Орден или это то, что делает человек, нажимая на элементы пользовательского интерфейса, чтобы инициировать действия в вашей системе?


Клиент - это актер, который делает много вещей, но с ним также связано много информации; Оправдывает ли это создание 2 отдельных классов, 1 как актер и 1 как объект данных?
IntelliData

@IntelliData, передавая богатые объекты по слоям, - вот где все становится сложнее. Если вы отправляете объект из контроллера в представление, представление должно понимать общий контракт объекта (например, стандарт JavaBeans). Если вы отправляете объект по проводу, JaxB или тому подобное должен иметь возможность восстановить его на немом объекте (потому что вы не дали им полный расширенный объект). Богатые объекты прекрасно подходят для манипулирования данными - они плохо переносят состояние. И наоборот, немые объекты плохо подходят для манипулирования данными и подходят для передачи состояния.

0

Я добавляю свои 2 цента здесь, упоминая подход, говорящий на SQL .

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

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

Наконец, я хотел бы отметить, что процесс поиска объектов очень похож на разложение системы на подсистемы . Оба основаны на поведении, а не на чем-то еще.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.