Rất nhiều người đã trả lời rồi. Nghĩ rằng tôi sẽ đưa ra quan điểm cá nhân của riêng tôi.
Đã có lần tôi làm việc trên một ứng dụng (và vẫn làm) tạo ra âm nhạc.
Ứng dụng có một bản tóm tắt Scale
lớp với nhiều lớp con: CMajor
, DMinor
vv Scale
nhìn một cái gì đó giống như vậy:
public abstract class Scale {
protected Note[] notes;
public Scale() {
loadNotes();
}
// .. some other stuff ommited
protected abstract void loadNotes(); /* subclasses put notes in the array
in this method. */
}
Các trình tạo nhạc đã làm việc với một thể hiện cụ Scale
thể để tạo nhạc. Người dùng sẽ chọn một tỷ lệ từ một danh sách, để tạo nhạc từ đó.
Một ngày nọ, một ý tưởng tuyệt vời xuất hiện trong đầu tôi: tại sao không cho phép người dùng tạo quy mô của riêng mình? Người dùng sẽ chọn ghi chú từ danh sách, nhấn nút và thang đo mới sẽ được thêm vào danh sách thang đo có sẵn.
Nhưng tôi đã không thể làm điều này. Đó là bởi vì tất cả các thang đo đã được đặt ở thời gian biên dịch - vì chúng được biểu thị dưới dạng các lớp. Sau đó, nó đánh tôi:
Nó thường trực quan để suy nghĩ theo nghĩa 'siêu lớp và lớp con'. Hầu hết mọi thứ có thể được thể hiện thông qua hệ thống này: siêu lớp Person
và các lớp con John
và Mary
; siêu lớp Car
và các lớp con Volvo
và Mazda
; siêu lớp Missile
và các lớp con SpeedRocked
, LandMine
và TrippleExplodingThingy
.
Nghĩ theo cách này là điều rất tự nhiên, đặc biệt đối với người tương đối mới với OO.
Nhưng chúng ta nên luôn nhớ rằng các lớp là các mẫu và các đối tượng là nội dung được đổ vào các mẫu này . Bạn có thể đổ bất cứ nội dung nào bạn muốn vào mẫu, tạo ra vô số khả năng.
Đây không phải là công việc của lớp con để điền vào mẫu. Đó là công việc của đối tượng. Công việc của lớp con là thêm chức năng thực tế hoặc mở rộng mẫu .
Và đó là lý do tại sao tôi nên tạo một Scale
lớp cụ thể , với một Note[]
trường và để các đối tượng điền vào mẫu này ; có thể thông qua các nhà xây dựng hoặc một cái gì đó. Và cuối cùng, vì vậy tôi đã làm.
Mỗi khi bạn thiết kế một mẫu trong một lớp (ví dụ: một Note[]
thành viên trống cần được điền hoặc một String name
trường cần được gán một giá trị), hãy nhớ rằng đó là công việc của các đối tượng của lớp này để điền vào mẫu ( hoặc có thể những người tạo ra các đối tượng này). Các lớp con có nghĩa là để thêm chức năng, không phải để điền vào các mẫu.
Bạn có thể bị cám dỗ để tạo ra một loại "siêu lớp Person
, lớp con John
và Mary
" hệ thống, giống như bạn đã làm, bởi vì bạn thích hình thức này mang lại cho bạn.
Bằng cách này, bạn chỉ có thể nói Person p = new Mary()
, thay vì Person p = new Person("Mary", 57, Sex.FEMALE)
. Nó làm cho mọi thứ có tổ chức hơn, và có cấu trúc hơn. Nhưng như chúng tôi đã nói, việc tạo một lớp mới cho mọi kết hợp dữ liệu không phải là cách tiếp cận tốt, vì nó làm mờ mã không có gì và giới hạn bạn về khả năng thời gian chạy.
Vì vậy, đây là một giải pháp: sử dụng một nhà máy cơ bản, thậm chí có thể là một nhà máy tĩnh. Thích như vậy:
public final class PersonFactory {
private PersonFactory() { }
public static Person createJohn(){
return new Person("John", 40, Sex.MALE);
}
public static Person createMary(){
return new Person("Mary", 57, Sex.FEMALE);
}
// ...
}
Bằng cách này, bạn có thể dễ dàng sử dụng 'cài đặt sẵn' 'đi kèm với chương trình', như vậy: Person mary = PersonFactory.createMary()
nhưng bạn cũng có quyền thiết kế người mới một cách linh hoạt, ví dụ trong trường hợp bạn muốn cho phép người dùng làm như vậy . Ví dụ:
// .. requesting the user for input ..
String name = // user input
int age = // user input
Sex sex = // user input, interpreted
Person newPerson = new Person(name, age, sex);
Hoặc thậm chí tốt hơn: làm một cái gì đó như vậy:
public final class PersonFactory {
private PersonFactory() { }
private static Map<String, Person> persons = new HashMap<>();
private static Map<String, PersonData> personBlueprints = new HashMap<>();
public static void addPerson(Person person){
persons.put(person.getName(), person);
}
public static Person getPerson(String name){
return persons.get(name);
}
public static Person createPerson(String blueprintName){
PersonData data = personBlueprints.get(blueprintName);
return new Person(data.name, data.age, data.sex);
}
// .. or, alternative to the last method
public static Person createPerson(String personName){
Person blueprint = persons.get(personName);
return new Person(blueprint.getName(), blueprint.getAge(), blueprint.getSex());
}
}
public class PersonData {
public String name;
public int age;
public Sex sex;
public PersonData(String name, int age, Sex sex){
this.name = name;
this.age = age;
this.sex = sex;
}
}
Tôi đã mang đi. Tôi nghĩ rằng bạn có được ý tưởng.
Các lớp con không có nghĩa là điền vào các mẫu được đặt bởi các siêu lớp của chúng. Các lớp con có nghĩa là để thêm chức năng . Đối tượng có nghĩa là điền vào các mẫu, đó là những gì họ đang làm.
Bạn không nên tạo một lớp mới cho mọi kết hợp dữ liệu có thể. (Giống như tôi không nên tạo một Scale
lớp con mới cho mọi kết hợp có thể có của Note
s).
Đây là một hướng dẫn: bất cứ khi nào bạn tạo một lớp con mới, hãy xem xét nếu nó thêm bất kỳ chức năng mới nào không tồn tại trong siêu lớp. Nếu câu trả lời cho câu hỏi đó là "không", thì bạn có thể đang cố gắng 'điền vào mẫu' của siêu lớp, trong trường hợp đó chỉ cần tạo một đối tượng. (Và có thể là một Nhà máy với 'cài đặt trước', để làm cho cuộc sống dễ dàng hơn).
Mong rằng sẽ giúp.