Як уникнути геттерів та сетерів?


85

Мені щось важко в розробці занять уоо. Я читав, що об’єкти викривають їх поведінку, а не їх дані; отже, замість використання getter / setters для зміни даних, методами даного класу повинні бути "дієслова" або дії, що діють на об'єкт. Наприклад, в об'єкті «Рахунки», ми мали б методи Withdraw()і Deposit()замість того , і setAmount()т.д. Див: Чому методи отримання і установки є злом .

Так, наприклад, враховуючи клас клієнта, який зберігає багато інформації про клієнта, наприклад, ім'я, DOB, тел, адреса тощо, як би уникнути отримання / встановлення всіх тих атрибутів? Який метод типу "поведінка" можна написати, щоб заповнити всі ці дані?




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

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

2
@Daniel Pryden: ім'я () може означати як встановити, чи отримати? ...
IntelliData

Відповіді:


55

Як зазначається в досить багатьох відповідях та коментарях, ЗНО є доцільним і корисним у деяких ситуаціях, особливо при передачі даних через межі (наприклад, серіалізація в JSON для надсилання через веб-сервіс). У решті цієї відповіді я більш-менш ігнорую це і розповім про доменні класи та про те, як вони можуть бути розроблені таким чином, щоб мінімізувати (якщо не усунути) геттерів та сеттерів, і все ще бути корисним у великому проекті. Я також не буду говорити про те, навіщо видаляти геттери чи сетери, або коли це робити, тому що це питання самі по собі.

Як приклад, уявіть, що ваш проект - це настільна гра на кшталт «Шахи» або «Бойовий корабель». У вас можуть бути різні способи подання цього шару в презентаційному шарі (консольний додаток, веб-служба, графічний інтерфейс тощо), але у вас також є основний домен. Ви можете мати один клас 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>>

Однак, із громадськими організаціями, які працюють із громадськими ресурсами, робити речі неправильно, просто витягуючи дані безпосередньо та використовуючи їх у порушення Tell, Don't Ask . Якщо використовувати цей шаблон, насправді простіше зробити це правильно: коли ви хочете створити поведінку, ви автоматично почнете, створюючи пов'язаний з нею тип. Порушення TDA буде дуже очевидним смердючим і, ймовірно, потребує розробки простішого, кращого рішення. На практиці ці пункти набагато простіше зробити це правильно, OO, ніж "злий" спосіб, який заохочують геттери.

Нарешті , навіть якщо це спочатку не очевидно, насправді можуть бути способи розкрити достатню кількість того, що вам потрібно, як поведінку, щоб уникнути необхідності виставляти стан. Наприклад, використовуючи нашу попередню версію 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 () чудовий, bcuz ур не має доступу до get / set, що дозволяє u змінити поля, задані DTO без порушення існуючого коду. 2. Скажіть, що Клієнт має достатньо поведінки, щоб виправдати його створення суб'єктом господарювання, яким чином ви отримаєте доступ до реквізитів, щоб змінити їх, наприклад, змінити адресу / tel тощо.
IntelliData

@IntelliData 1. Коли ви говорите "змінити поля", ви маєте на увазі змінити визначення класу або мутувати дані? Останнього можна просто уникнути, видаляючи загальнодоступні сетери, але залишаючи getters, тому аспект dto не має значення. Перша насправді не є цілою причиною того, що громадські працівники "злі". Наприклад, дивіться програмісти.stackexchange.com/questions/ 157526/….
Бен Аронсон

@IntelliData 2. На це важко відповісти, не знаючи поведінки. Але, мабуть, відповідь така: ви б цього не зробили. Яку поведінку може мати Customerклас, який вимагає можливості вимкнути його номер телефону? Можливо, телефонний номер замовника змінюється, і мені потрібно наполегливо змінити цю базу даних, але жодне з них не несе відповідальність за доменний об'єкт, що надає поведінку. Це питання щодо доступу до даних, і він, ймовірно, обробляється з DTO і, скажімо, сховищем.
Бен Аронсон

@IntelliData Зберігання Customerданих об’єкта домену відносно свіжими (синхронізованими з db) - це питання управління його життєвим циклом, що також не є власною відповідальністю, і, ймовірно, знову опиниться в сховищі, на заводі або в контейнері IOC або що інстанціонує Customers.
Бен Аронсон

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

71

Найпростіший спосіб уникнути задачі - передати значення методу конструктора під newчас об'єкта. Це також звичайна закономірність, коли ви хочете зробити предмет незмінним. Однак це не завжди є зрозумілим у реальному світі.

Це правда, що методи повинні стосуватися поведінки. Однак деякі об'єкти, як-от Замовник, існують насамперед для зберігання інформації. Це ті види об’єктів, які отримують найбільше користь від геттерів та сетерів; якби взагалі не було потреби в таких методах, ми б просто їх взагалі усунути.

Подальше читання,
коли виправдовуються геттери та сетери


3
То чому всі галасливість про "Геттер / сеттер" - зла?
IntelliData

46
Просто звичайна понтифікація розробниками програмного забезпечення, які повинні знати краще. Для статті, яку ви пов’язали, автор використовує фразу "геттери і сетери - це зло", щоб привернути вашу увагу, але це не означає, що твердження є категорично правдивим.
Роберт Харві

12
@IntelliData деякі люди скажуть вам, що сама Java - це зло.
недійсне

18
@MetaFightsetEvil(null);

4
Безумовно, екваль - це Втілене Зло. Або близького двоюрідного брата.
єпископ

58

Цілком прекрасно мати об'єкт, який викриває дані, а не поведінку. Ми просто називаємо це "об'єктом даних". Шаблон існує під такими іменами, як об'єкт передачі даних або об'єкт значення. Якщо метою об’єкта є зберігання даних, то для доступу до даних діючі та сеттери дійсні.

То чому б хтось сказав "геттер та сетер методи - це зло"? Ви побачите це багато - хтось приймає вказівки, які цілком дійсні в конкретному контексті, а потім видаляють контекст, щоб отримати більш важкий заголовок. Наприклад, " користь композиції над спадщиною " є прекрасним принципом, але досить скоро хтось збирається видалити контекст і написати " Чому продовжує це зло " (ей, той же автор, який збіг!) Або " Спадщина - це зло і повинно бути знищено ».

Якщо ви подивитесь на зміст статті, вона насправді має деякі достовірні моменти, вона просто розтягне крапку, щоб зробити заголовок кліку baity. Наприклад, у статті зазначено, що деталі впровадження не повинні піддаватися впливу. Це принципи інкапсуляції та приховування даних, які є основними в ОО. Однак метод getter за визначенням не розкриває деталі реалізації. У випадку з об’єктом даних Клієнта властивості Імені , Адреси тощо - це не деталі реалізації, а цілі цілі об'єкта і повинні бути частиною загальнодоступного інтерфейсу.

Прочитайте продовження статті, на яку ви посилаєтесь, щоб побачити, як він пропонує насправді встановити такі властивості, як "ім'я" та "зарплата" на об'єкт "Співробітник", не використовуючи злих сеттерів. Виявляється, він використовує шаблон із "Експортером", який заповнений методами " Додати ім'я", " Додати заробітну плату", який, у свою чергу, встановлює однойменні поля ... Отже, врешті-решт, він закінчує, використовуючи точно схему setter, просто з різні умови іменування.

Це так, як думати, що ви уникаєте підводних каменів, перейменовуючи їх там, не змінюючи при цьому, зберігаючи ту саму реалізацію.


На жаль, так звані експерти кажуть, що це не так.
IntelliData

8
Знову ж таки, деякі люди сказали: "Ніколи не використовуй OOP": шкідливо.cat-v.org
software/

7
FTR, я думаю, що більше "експертів" все ще навчають безглуздо створювати геттерів / сеттерів для всього , ніж ніколи взагалі не створювати. ІМО, остання - менш оманливі поради.
близько

4
@leftaroundabout: Гаразд, але я пропоную середину між "завжди" і "ніколи", що є "використовувати, коли це доречно".
ЖакB

3
Я думаю, що проблема полягає в тому, що багато програмістів перетворюють кожен об'єкт (або занадто багато об'єктів) у DTO. Іноді вони необхідні, але уникайте їх якомога більше, оскільки вони відокремлюють дані від поведінки. (Припустимо, що ви побожний OOPer)
user949300

11

Для перетворення Customerкласу з об’єкта даних ми можемо задати собі такі запитання щодо полів даних:

Як ми хочемо використовувати {поле даних}? Де використовується {поле даних}? Чи можна і чи слід перенести використання {data field} до класу?

Наприклад:

  • Яка мета Customer.Name?

    Можливі відповіді, відображайте ім’я на веб-сторінці для входу, використовуйте його в розсилці до замовника.

    Що призводить до методів:

    • Customer.FillInTemplate (…)
    • Customer.IsAppvableForMailing (…)
  • Яка мета Customer.DOB?

    Перевірка віку клієнта. Знижки на день народження замовника. Розсилки.

    • Customer.IsAppvableForProduct ()
    • Customer.GetPersonalDiscount ()
    • Customer.IsAppvableForMailing ()

З огляду на коментарі, приклад-об’єкт Customer- і як об'єкт даних, і як "реальний" об'єкт із власними обов'язками - занадто широкий; тобто вона має занадто багато властивостей / відповідальності. Що призводить до або безлічі компонентів залежно від Customer(зчитування його властивостей) або Customerзалежно від партії компонентів. Можливо, існують різні погляди клієнта, можливо, кожен повинен мати свій окремий клас 1 :

  • Клієнт у контексті Accountгрошових операцій, ймовірно, звик лише:

    • допомогти людям визначити, що їх грошовий переказ йде правильній людині; і
    • група Accountс.

    Цей клієнт не потребує в області , як DOB, FavouriteColour, Tel, і , можливо , навіть не Address.

  • Клієнт у контексті користувача, який входить на банківський веб-сайт.

    Відповідні поля:

    • FavouriteColour, які можуть надходити у формі персоналізованої тематизації;
    • LanguagePreferences, і
    • GreetingName

    Замість властивостей з getters та setters вони можуть бути захоплені одним методом:

    • PersonaliseWebPage (сторінка шаблону);
  • Замовник у контексті маркетингу та персоналізованої розсилки.

    Тут не покладаючись на властивості об’єкта даних, а натомість виходячи з обов'язків об’єкта; наприклад:

    • IsCustomerInterestedInAction (); і
    • GetPersonalDiscounts ().

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


1. Звичайно, Customerі Accountкласи були прикладами, і для простого прикладу або домашнього завдання, розділення цього клієнта може бути надмірним, але на прикладі розбиття я сподіваюсь продемонструвати, що метод перетворення об’єкта даних на об’єкт із обов'язки спрацюють.


4
Оновлення, тому що ви насправді відповідаєте на питання :) Однак очевидно, що запропоновані рішення є набагато гіршими, ніж просто отримання gettes / setters - наприклад, FillInTemplate чітко порушує принцип розділення проблем. Що просто свідчить про те, що передумови цього питання хибні.
ЖакБ

@Kasper van den Berg: І коли у вас є багато атрибутів у Замовника, як це зазвичай буває, як би ви їх спочатку встановили?
IntelliData

2
@IntelliData ваші значення, ймовірно, надходять із бази даних, XML і т.д. Не ідеально, але зазвичай можна уникати публічних сетерів. (Дивіться мою відповідь для отримання більш детальної інформації)
user949300

6
Я не думаю, що клас клієнтів не повинен знати про будь-яку з цих речей.
CodesInChaos

А як щодо чогось Customer.FavoriteColor?
Гейб

8

TL; DR

  • Моделювання поведінки добре.

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

  • Іноді потрібні об'єкти даних.


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

Є кілька причин уникати геттерів та сетерів. Одне, як ви зазначали, уникати моделювання даних. Це насправді другорядна причина. Більшою причиною є надання абстракції.

У вашому прикладі з банківським рахунком зрозуміло: setBalance()Метод був би дуже поганим, оскільки встановити баланс - це не те, для чого слід використовувати рахунок. Поведінка облікового запису повинна максимально абстрагуватися від поточного балансу. Він може враховувати баланс, приймаючи рішення про те, чи не буде знято кошти, він може надати доступ до поточного балансу, але зміна взаємодії з банківським рахунком не повинна вимагати від користувача розрахунку нового залишку. Це те, що рахунок повинен зробити сам.

Навіть пара deposit()та withdraw()методи не є ідеальними для моделювання банківського рахунку. Кращим способом було б надати лише один transfer()метод, який враховує інший рахунок та суму як аргументи. Це дозволило б класу облікових записів тривіально забезпечити, щоб ви не випадково створили / знищили гроші у вашій системі, це забезпечило б дуже корисну абстракцію, а це фактично надасть користувачам більше розуміння, оскільки це змусить використовувати спеціальні облікові записи для зароблені / інвестовані / втрачені гроші (див. подвійний облік ). Звичайно, не кожне використання облікового запису потребує такого рівня абстрагування, але, безумовно, варто враховувати, скільки абстракцій можуть надати ваші заняття.

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


Клас замовника

Клієнт - це, безумовно, організація у вашій системі, яка повинна намагатися надати корисні абстракції. Наприклад, це, ймовірно, повинно бути пов’язане з кошиком для покупок, а поєднання кошика та клієнта повинно дозволяти здійснювати покупку, що може почати такі дії, як надсилання йому потрібних товарів, стягнення з нього грошей (з урахуванням обраного платежу метод) тощо.

Проблема полягає в тому, що всі згадані вами дані пов'язані не лише з клієнтом, але й ці дані також можуть бути зміненими. Замовник може переїхати. Вони можуть змінити свою кредитну карту компанії. Вони можуть змінити свою електронну адресу та номер телефону. Чорт, вони можуть навіть змінити ім'я та / або стать! Таким чином, повнофункціональний клас клієнтів справді повинен забезпечити повний доступ до всіх цих даних.

Тим не менш, сетери можуть / повинні надавати нетривіальні послуги: Вони можуть забезпечити правильний формат адрес електронної пошти, перевірку поштових адрес тощо. Так само "геттери" можуть надавати послуги високого рівня, такі як надання адрес електронної пошти у Name <user@server.com>форматі використовуючи поля імен та накладену електронну адресу, або надайте правильно відформатовану поштову адресу тощо. Звичайно, що від цього функціоналу високого рівня має сенс, сильно залежить від вашого випадку використання. Це може бути повний надмір, або може закликати інший клас зробити це правильно. Вибір рівня абстракції не з легких.


звучить правильно, хоча я не погоджуюсь щодо статевої частини ...;)
IntelliData

6

Намагаючись розширити відповідь Каспера, найлегше здиратися і ліквідувати сетерів. Доволі невиразним, рукотворним (і, сподіваюся, жартівливим) аргументом:

Коли клієнтське ім'я колись зміниться?

Рідко. Можливо, вони одружилися. Або пішли в захист свідків. Але в цьому випадку ви також хочете перевірити та, можливо, змінити місце проживання, поруч із родиною та іншою інформацією.

Коли DOB колись зміниться?

Тільки при первинному створенні, або на виправлення даних. Або якщо вони бейсболіст Домінкан. :-)

Ці поля не повинні бути доступними звичайними, звичайними сетерами. Можливо, у вас є 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, що є абстрактним стандартом , навряд чи зміниться. Коли ви змінюєте базову базу даних або змінюєте схему, Клієнт не змінюється , але CustomerDBHelper робить. Якщо вам пощастило, змінюється лише AbstractMySQLHelper, який автоматично вносить зміни для клієнта, продавця, доставки тощо ...

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

І, головний пункт статті Голуба, порівняйте та порівняйте вищезгадане з тим, як було б, якби ви використали геттери та сетери для всього та змінили базу даних.

Так само, скажімо, ви використовуєте багато XML. IMO, чудово поєднати свого Замовника з таким абстрактним стандартом, як Python xml.etree.ElementTree або Java org.w3c.dom.Element . Замовник отримує і встановлює себе від цього. Знову ж таки, ви можете (можливо) зменшити потребу в геттерах та сетерах.


Ви б сказали, що доцільно використовувати зразок Builder?
IntelliData

Будівельник корисний для того, щоб зробити конструкцію об'єкта простішою і міцнішою, і, за бажанням, дозволити незмінності об'єкта. Однак він все-таки (частково) виявляє той факт, що в нижньому об'єкті є поле DOB, тому це не все-все-всьому.
user949300

1

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

У зв'язку з тим, що існує багато способів зберігання / отримання даних, і ви хочете від'єднати об'єкти даних від способу їх зберігання, інкапсуляція може бути "скомпрометована", зробивши ці учасники загальнодоступними, або зробивши їх доступними за допомогою Getters і сетери, що майже так само погано, як оприлюднення їх.

Навколо цього існують різні способи. Один із способів - зробити дані доступними "другові". Хоча дружба не успадковується, це можна подолати будь-яким серіалізатором, який вимагає інформацію від друга, тобто базовим серіалізатором, який "пересилає" інформацію.

У вашому класі може бути загальний метод "fromMetadata" або "toMetadata". З метаданих будується об’єкт, тому він цілком може бути конструктором. Якщо мова є динамічно набраною мовою, метадані є досить стандартними для такої мови і, ймовірно, є основним способом побудови таких об'єктів.

Якщо вашою мовою є спеціально C ++, один із способів цього полягає у тому, щоб мати загальнодоступну "структура" даних, а потім для вашого класу мати примірник цього "структура" як члена, а фактично всі дані, які ви збираєтесь зберігати / отримати для зберігання в ньому. Потім ви можете легко записати "обгортки" для читання / запису ваших даних у різних форматах.

Якщо у вашій мові є C # або Java, які не мають "структур", ви можете зробити так само, але ваша структура тепер є другорядним класом. Немає реальної концепції "власності" на дані чи постійність, тому якщо ви видаєте екземпляр класу, що містить ваші дані, і він є загальнодоступним, все, що може отримати, може змінити його. Ви можете "клонувати" це, хоча це може бути дорогим. Крім того, ви можете зробити так, щоб цей клас мав приватні дані, але користувався доступом. Це дає користувачам вашого класу обхідний спосіб дістатися до даних, але це не прямий інтерфейс з вашим класом, а насправді є деталлю для зберігання даних класу, що також є корисним випадком.


0

OOP - це про інкапсуляцію та приховування поведінки всередині об'єктів. Об'єкти - чорні скриньки. Це спосіб дизайну речі. В багатьох випадках актив не потрібно знати внутрішній стан іншого компонента, і краще не знати про це. Ви можете застосувати цю ідею, використовуючи в основному інтерфейси або всередині об'єкта з видимістю, і опікуючись лише дозволеними дієсловами / діями, доступні абоненту.

Це добре працює для якоїсь проблеми. Наприклад, в інтерфейсах користувача для моделювання окремих компонентів інтерфейсу користувача. Коли ви спілкуєтесь із текстовим полем, вас цікавить лише налаштування тексту, отримання його чи прослуховування події зміни тексту. Зазвичай вас не цікавить, де знаходиться курсор, шрифт, який використовується для малювання тексту або як використовується клавіатура. Тут інкапсуляція багато дає.

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

У цьому випадку або багатьох інших (наприклад, доступ до бази даних) ви дійсно працюєте із спільними даними. Як такої немає причин ховати її, навпаки, ви хочете зробити її доступною. Проблеми з доступом для читання / запису чи послідовності перевірки даних можуть бути занепокоєні, але в цьому суть, основна концепція, якщо вона є загальнодоступною.

Для такої вимоги до дизайну, коли ви хочете уникнути інкапсуляції та зробити речі загальнодоступними та зрозумілими, ви хочете уникати об'єктів. Насправді вам потрібні кортежі, C структури або їх еквівалент, а не об'єкти.

Але це також трапляється на таких мовах, як Java, єдине, що можна моделювати, - це об'єкти чи масиви об’єктів. Об'єкти в них можуть містити кілька тубільних типів (int, float ...), але це все. Але об'єкти також можуть поводитись як прості структури з просто публічними полями і все.

Тож якщо ви модеруєте дані, ви можете виконати лише загальнодоступні поля всередині об’єктів, тому що більше вам не потрібно. Ви не використовуєте інкапсуляцію, оскільки вона вам не потрібна. Це робиться таким чином на багатьох мовах. В java, історично, стандартна троянда, де за допомогою геттера / сеттера ви могли хоча б мати контроль читання / запису (не додаючи сеттер наприклад), і що інструменти та фреймворк, використовуючи API інстроспекції, шукатимуть методи геттера / сеттера і використовувати його для автоматично заповнювати вміст або відображати тези як модифіковані поля в автоматично створеному користувальницькому інтерфейсі.

Є також аргумент, який ви можете додати деяку логіку / перевірку в методі setter.

Насправді майже немає виправдань для геттерів / сетерів, оскільки вони найчастіше використовуються для моделювання чистих даних. Рамки та розробники, що використовують ваші об'єкти, сподіваються, що геттер / сетер все одно не будуть робити нічого, крім встановлення / отримання полів. Ви ефективно не робите більше з геттером / сетером, ніж те, що можна зробити з публічними полями.

Але це старі звички і старі звички важко усунути ... Ви навіть можете погрожувати колегам чи викладачеві, якщо не поставите гетерів / сетер наосліп, якщо їм не вистачає фону, щоб краще зрозуміти, що вони є і які вони ні.

Вам, ймовірно, доведеться змінити мову, щоб позбутися всіх кодів котлоплайдаторів / установників. (Як, наприклад, C # або lisp). Для мене геттери / сетери - це ще одна помилка на мільярд доларів ...


6
Властивості C # насправді не реалізують інкапсуляцію більше, ніж геттери та сетери ...
IntelliData

1
Перевага ефірів [gs] полягає в тому, що ви можете робити те, чого зазвичай не робити (перевіряти значення, сповіщати спостерігачів), імітувати неіснуючі поля тощо, і головним чином, що ви можете їх змінити пізніше . З Ломбок , то шаблонний пішов: @Getter @Setter class MutablePoint3D {private int x, y, z;}.
maaartinus

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

@ IntelliData C # властивості дозволяють не писати 1 непотрібний єдиний символ котельної панелі та не піклуватися про всі ці речі про геттер / сеттер ... Це вже краще досягнення, ніж ломбок проекту. Крім того, для мене POJO з просто дітьми / сеттером не тут для забезпечення інкапсуляції, а для того, щоб опублікувати формат даних, який можна вільно читати чи писати як обмін з сервісом. Потім інкапсуляція є протилежною вимозі проектування.
Ніколя Буске

Я не думаю, що властивості справді хороші. Звичайно, ви зберігаєте префікс і круглі дужки, тобто 5 символів на виклик, але 1. вони виглядають як поля, що заплутано. 2. вони додаткова річ, для якої вам потрібна підтримка в роздумах. Ніякої великої справи, але немає і великої переваги (якщо порівнювати з Java + Lombok; чиста Java - це явна втрата).
maaartinus

0

Так, наприклад, враховуючи клас клієнта, який зберігає багато, якщо інформація про клієнта, наприклад, ім’я, DOB, тел, адреса тощо, як би уникнути отримання / встановлення всіх тих атрибутів? Який метод типу "поведінка" можна написати, щоб заповнити всі ці дані?

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

Не плутайте Customerяк клас об’єктів із "Клієнт" як користувач / актор, який виконує різні завдання за допомогою вашого програмного забезпечення.

Коли ви кажете, що дається Клас клієнтів, який зберігає багато, якщо інформація про клієнта, то, наскільки це стосується поведінки, це виглядає так, що ваш клас Клієнта мало відрізняє його від гірської породи. А Rockможе мати колір, ви можете дати йому ім'я, ви могли б мати поле для зберігання його поточної адреси, але ми не очікуємо, що від скелі виникає якась розумна поведінка.

Із пов’язаної статті про те, що геттери / сетери є злими:

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

Без будь-якої визначеної поведінки посилання на скелю як на Customerне змінює той факт, що це просто об’єкт із деякими властивостями, які ви хотіли б відстежувати, і не має значення, якими трюками ви хочете грати, щоб відволіктися від геттерів і сетери. Рок не байдуже, чи має він дійсне ім'я, і ​​не слід очікувати, що рок буде знати, чи адреса є дійсною чи ні.

Ваша система замовлень може асоціювати 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.