Пізня відповідь, але я не можу встояти.
Чи є більшість X класів на Y хорошим чи анти-зразком?
У більшості випадків більшість правил, що застосовуються без роздумів, здебільшого зроблять жахливі помилки (включаючи це).
Дозвольте розповісти вам історію про народження об'єкта в хаосі якогось правильного, швидкого та брудного, процесуального кодексу, який стався, не задумом, а з відчаю.
Я з моїм стажистом програмуємо пару, щоб швидко створити якийсь код, що викидається, щоб скребти веб-сторінку. У нас немає абсолютно ніяких причин очікувати, що цей код буде жити довго, тому ми просто вибиваємо щось, що працює. Ми захоплюємо всю сторінку як рядок і рубаємо потрібні нам речі на самий дивовижно крихкий спосіб, який ви могли собі уявити. Не судіть. Це працює.
Тепер, роблячи це, я створив кілька статичних методів, щоб зробити рубання. Мій стажер створив клас DTO, який був дуже схожий на ваш CatData
.
Коли я вперше подивився на DTO, він мене помилив. Роки збитків, які Java завдала моєму мозку, змусили мене відскочити на публічних полях. Але ми працювали в C #. C # не потребує передчасних жителів та сетерів, щоб зберегти ваше право зробити дані непорушними або інкапсульованими пізніше. Не змінюючи інтерфейс, ви можете додавати їх коли завгодно. Можливо, просто так можна встановити точку перерви. Все, не кажучи про це своїм клієнтам. Так C #. Бу Java.
Так я тримав язик. Я спостерігав, як він використовував мої статичні методи, щоб ініціалізувати цю річ перед її використанням. У нас їх було близько 14. Це було потворно, але у нас не було причин для турботи.
Тоді нам це було потрібно в інших місцях. Ми виявили, що хочемо скопіювати та вставити код. 14 рядків ініціалізації прокручуються навколо. Це починало боліти. Він вагався і запитував у мене ідеї.
Я неохоче запитав: "Чи вважаєте ви предмет?"
Він озирнувся до свого ДОТ і розгублено вивернув обличчя. "Це об'єкт".
"Я маю на увазі реальний об'єкт"
"А?"
"Дозвольте мені щось показати. Ви вирішите, чи це корисно"
Я вибрав нове ім’я і швидко проскочив щось таке, що виглядало приблизно так:
public class Cat{
CatData(string catPage) {
this.catPage = catPage
}
private readonly string catPage;
public string name() { return chop("name prefix", "name suffix"); }
public string weight() { return chop("weight prefix", "weight suffix"); }
public string image() { return chop("image prefix", "image suffix"); }
private string chop(string prefix, string suffix) {
int start = catPage.indexOf(prefix) + prefix.Length;
int end = catPage.indexOf(suffix);
int length = end - start;
return catPage.Substring(start, length);
}
}
Це нічого не робило, ніж статичні методи вже не робили. Але тепер я всмоктав 14 статичних методів у клас, де вони могли бути на самоті з даними, над якими працювали.
Я не змусив свого стажера використовувати його. Я просто запропонував це і нехай він вирішить, чи хоче він дотримуватися статичних методів. Я пішов додому, думаючи, що він, мабуть, буде дотримуватися того, що вже працював. Наступного дня я виявив, що він використовує це в купі місць. Це знешкодило решту коду, який все ще був некрасивим і процедурним, але ця складна частина тепер була прихована від нас за об'єктом. Це було трохи краще.
Тепер переконайтеся, що кожен раз, коли ви звертаєтесь до цього, це робить неабияку роботу. DTO - хороша швидкість кешованого значення. Я хвилювався з цього приводу, але зрозумів, що можу додати кешування, якщо нам коли-небудь знадобиться, не торкаючись жодного використовуючого коду. Тож я не збираюся турбувати, поки нас не хвилює.
Я кажу, що ви завжди повинні дотримуватися об'єктів OO над DTO? Ні. Блиск DTO, коли вам потрібно перейти межу, яка не дає вам рухатися методами. DTO мають своє місце.
Але так роблять об'єкти ОО. Дізнайтеся, як використовувати обидва інструменти. Дізнайтеся, що коштує кожен. Навчіться вирішувати проблеми, ситуацію та стажера. Догма тут не твій друг.
Оскільки моя відповідь вже смішно довгий, дозвольте мені заборонити вас про деякі помилки з оглядом вашого коду.
Наприклад, у класі зазвичай є члени та методи класу, наприклад:
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Де твій конструктор? Це не показує мене достатньо, щоб знати, чи це корисно.
Але прочитавши про принцип єдиної відповідальності та відкритий закритий принцип, я віддаю перевагу розділити клас на DTO та helper class лише статичними методами, наприклад:
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Я думаю, що він відповідає принципу єдиної відповідальності, оскільки зараз CatData зобов'язаний зберігати дані, не дбаючи про методи (також для CatMethods).
Ви можете зробити багато дурних речей в ім'я Принципу єдиної відповідальності. Я можу стверджувати, що котячі струни та котячі вставки повинні бути розділені. У всіх методах малювання та зображень має бути свій клас. За вашу запущену програму покладається одна відповідальність, тому вам слід мати лише один клас. : P
Для мене найкращий спосіб дотримуватися принципу єдиної відповідальності - це знайти хорошу абстракцію, яка дозволяє скласти складність у коробці, щоб ви могли її заховати. Якщо ви можете назвати це добре ім’я, яке не дасть людям дивуватися тому, що вони знаходять, коли вони заглядають всередину, ви досить добре стежили за цим. Очікуючи, що він продиктує більше рішень, то це вимагає неприємностей. Чесно кажучи, обидва ваші списки коду роблять це, тому я не розумію, чому тут має значення SRP.
І він також відповідає принципу відкритого закритого типу, оскільки для додавання нових методів не потрібно змінювати клас CatData.
Ну ні. Принцип відкритого закриття не полягає у додаванні нових методів. Йдеться про те, що можна змінити реалізацію старих методів і нічого не потрібно редагувати. Нічого, що використовує вас, і не ваші старі методи. Натомість ви пишете якийсь новий код десь в іншому місці. Якась форма поліморфізму зробить це чудово. Не бачимо цього тут.
Моє запитання: добре це чи анти-модель?
Ну чорт, як я маю знати? Подивіться, робити це в будь-якому випадку має переваги та витрати. Коли ви відокремлюєте код від даних, ви можете змінити або без перекомпілювання іншого. Можливо, це для вас критично важливо. Можливо, це просто робить ваш код безглуздо складним.
Якщо ви відчуваєте себе краще, ви далеко не те, що Мартін Фаулер називає об'єктом параметра . Не потрібно брати у свій об’єкт лише примітивів.
Я б хотів, щоб ви зробили сенс для того, як зробити своє розлучення, чи ні, в будь-якому стилі кодування. Тому що вірите чи ні, вас не змушують вибирати стиль. Вам просто потрібно жити зі своїм вибором.