Чи є хорошою практикою уникати констант за допомогою геттерів?


25

Чи є хорошою практикою замінити константи, які використовуються поза класами, геттерами?

Як приклад, краще використовувати if User.getRole().getCode() == Role.CODE_ADMINабо if User.getRole().isCodeAdmin()?

Це призвело б до цього класу:

class Role {
    constant CODE_ADMIN = "admin"
    constant CODE_USER = "user"

    private code

    getRoleCode() {
       return Role.code
    }

    isCodeAdmin () {
       return Role.code == Role.CODE_ADMIN
    }

    isCodeUser () {
       return Role.code == Role.CODE_USER
    }
}

15
Я вважаю за краще використовувати щось подібне User.HasRole(Role.Admin).
CodesInChaos


4
Перевірте принцип Tell Don't Ask .
Енді,

Я сумніваюсь у передумові: User.getRole().getCode()це вже неприємне прочитання, порівняння коду з роллю робить його ще більш негідним.
msw

Відповіді:


47

По-перше, зауважте, що робити щось на зразок entity.underlyingEntity.underlyingEntity.method()вважається кодовим запахом згідно із Законом Деметера . Таким чином, ви виставляєте споживачам багато деталей щодо впровадження. І кожна потреба в розширенні чи модифікації такої системи зашкодить сильно.

Отже, враховуючи це, я рекомендую вам мати HasRoleабо IsAdminметод для Userкоментаря CodesInChaos. Таким чином, спосіб реалізації ролей на користувачеві залишається детальним для реалізації для споживача. А також більш природно запитувати користувача, яка його роль, а не розпитувати його про деталі його ролі, а потім приймати рішення на основі цього.


Будь ласка, уникайте використання strings, якщо це не потрібно. nameє хорошим прикладом stringзмінної, оскільки вміст заздалегідь невідомий. З іншого боку, щось подібне до того, roleде у вас є два чіткі значення, добре відомі під час компіляції, краще використовувати сильне введення тексту. Ось де переживає тип перерахування ...

Порівняйте

public bool HasRole(string role)

з

public enum Role { Admin, User }

public bool HasRole(Role role)

Другий випадок дає мені набагато більше уявлення про те, що мені слід пройти навколо. Це також заважає мені помилково перейти до інваліда, stringякщо я не мав уявлення про ваші константи ролі.


Далі - рішення про те, як буде виглядати роль. Ви можете використовувати enum, безпосередньо збережений у користувача:

public enum Role
{
    Admin,
    User
}

public class User
{
    private Role _role;

    public bool HasRole(Role role)
    {
        return _role == role;
    }

    // or
    public bool IsAdmin()
    {
        return _role == Role.Admin;
    }
}

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

public enum RoleType
{
    User,
    Admin
}

public class Role
{
    private RoleType _roleType;

    public bool IsAdmin()
    {
        return _roleType == RoleType.Admin;
    }

    public bool IsUser()
    {
        return _roleType == RoleType.User;
    }

    // more role-specific logic...
}

public class User
{
    private Role _role;

    public bool IsAdmin()
    {
        return _role.IsAdmin();
    }

    public bool IsUser()
    {
        return _role.IsUser();
    }
}

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

Згідно з вашим запитанням, я думаю, вам краще піти з першим варіантом із перерахунком безпосередньо User. Якщо вам потрібна більше логіки щодо Role, другий варіант слід розглядати як вихідний пункт.


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

2
Re "instance.property.property.method () ..." Хіба це не рідина ?
Пітер Мортенсен

2
@PeterMortensen Це інше, ніж вільний інтерфейс. Вільний інтерфейс типу X, безумовно, дозволить вам робити рядки функціональних викликів X.foo().bar().fee().... У цьому плавному інтерфейсі foo, bar та fee - це функції всередині класу, що Xповертають об'єкт типу X. Але для 'instance.property.property.method (), згаданого в цьому прикладі, два propertyвиклики фактично будуть в окремих класах. Проблема полягає в тому, що ви проходите через кілька шарів абстракції, щоб одержати деталі низького рівня.
Шаз

10
Хороша відповідь, але Закон Деметера не є вправним рахунком. instance.property.property.method()не обов'язково є порушенням або навіть кодовим запахом; це залежить від того, чи працюєте ви з об'єктами чи структурами даних. Node.Parent.RightChild.Remove()мабуть, не є порушенням LoD (хоча смердючі з інших причин). var role = User.Role; var roleCode = role.Code; var isAdmin = roleCode == ADMIN;майже напевно є порушенням, незважаючи на те, що я завжди "використовував лише одну крапку".
Карл Лет

1
Я розумію, що instance.property.property.method()це порушення Міністерства законодавства, але в ОП це instance.method().method()має бути добре. У вашому останньому прикладі є стільки кодового коду, Userщо він служить лише фасадом для Role.
Бергі

9

Мабуть, мабуть, є функція перевірити, чи зберігається код адміністраторського коду. Те, що ви насправді хочете дізнатись, - чи є ця людина адміністратором. Отже, якщо ви не хочете виставляти константи, ви також не повинні викривати, що існує код, і називати методи isAdmin () та isUser ().

Це сказало: "якщо User.getRole (). GetCode () == Role.CODE_ADMIN" насправді є пригоршею лише для перевірки того, що користувач є адміністратором. Скільки речей повинен забути розробник, щоб написати цей рядок? Він або вона повинна пам'ятати, що користувач має роль, роль - код, а клас "Ролі" має константи для кодів. Це багато інформації, яка суто стосується реалізації.


3
Гірше: те, що користувач завжди має рівно одну роль, ні більше, ні менше.
Дедуплікатор

5

На додаток до того, що вже розмістили інші, слід пам’ятати, що використання константи безпосередньо має ще один недолік: якщо щось змінить спосіб поводження з правами користувача, усі ці місця також потрібно змінити.

І це робить жахливо підсилювати. Можливо, ви хотіли б в один момент мати супер- тип користувача , який, очевидно, також має права адміністратора. Інкапсуляція - це в основному однолінійний додаток.

Він не тільки короткий і чистий, він також простий у використанні та розумінні. І - можливо, найбільше - важко помилитися.


2

Хоча я багато в чому погоджуюся з пропозиціями уникати констант і використовувати метод isFoo()тощо, один можливий контрприклад.

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

Зауважте, що використання enums або hasRole()уникає необхідності писати сотні методів, тож це найкраще з усіх можливих у світі.


2

Я не думаю, що жоден із представлених вами варіантів є принципово неправильним.

Я бачу, ви не запропонували одне, що я б назвав абсолютно невірним: жорстке кодування рольових кодів у функціях поза класом Role. Це є:

if (user.getRole().equals("Administrator")) ...

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

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

public bool isActuary() { return code==Role.ACTUARY; }
public bool isAccountant() { return code==Role.ACTUARY; }
... etc ...

Особисто я також уникав ланцюгів дзвінків. Я б краще писав

if (user.getRole().equals(Role.FOOBATER))

потім

if (user.getRole().getRoleCode()==Role.FOOBATER_CODE)

і в цей момент чому записку пишуть:

if (user.hasRole(Role.FOOBATER))

Потім дайте класу User знати, як перевірити роль.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.