Banyak orang sudah menjawab. Kupikir aku akan memberikan perspektif pribadiku sendiri.
Sekali waktu saya mengerjakan sebuah aplikasi (dan masih melakukannya) yang menciptakan musik.
Aplikasi ini memiliki abstrak Scale
kelas dengan beberapa subclass: CMajor
, DMinor
, dll Scale
tampak sesuatu seperti:
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. */
}
Generator musik bekerja dengan Scale
instance spesifik untuk menghasilkan musik. Pengguna akan memilih skala dari daftar, untuk menghasilkan musik.
Suatu hari, sebuah ide keren muncul di benak saya: mengapa tidak mengizinkan pengguna untuk membuat skala sendiri? Pengguna akan memilih catatan dari daftar, tekan tombol, dan skala baru akan ditambahkan ke daftar skala yang tersedia.
Tetapi saya tidak dapat melakukan ini. Itu karena semua skala sudah ditetapkan pada waktu kompilasi - karena mereka dinyatakan sebagai kelas. Lalu aku tersadar:
Seringkali intuitif untuk berpikir dalam istilah 'superclasses and subclasses'. Hampir semuanya dapat diekspresikan melalui sistem ini: superclass Person
dan subclass John
dan Mary
; superclass Car
dan subclass Volvo
dan Mazda
; superclass Missile
dan subclass SpeedRocked
, LandMine
dan TrippleExplodingThingy
.
Sangat wajar untuk berpikir seperti ini, terutama bagi orang yang relatif baru mengenal OO.
Tetapi kita harus selalu ingat bahwa kelas adalah template , dan objek adalah konten yang dituangkan ke dalam template ini . Anda dapat menuangkan konten apa pun yang Anda inginkan ke dalam templat, menciptakan kemungkinan yang tak terhitung jumlahnya.
Bukan tugas subclass untuk mengisi templat. Ini pekerjaan dari objek. Tugas subclass adalah menambahkan fungsionalitas aktual , atau memperluas templat .
Dan itu sebabnya saya seharusnya membuat Scale
kelas yang konkret , dengan Note[]
bidang, dan membiarkan objek mengisi template ini ; mungkin melalui konstruktor atau sesuatu. Dan akhirnya, jadi saya melakukannya.
Setiap kali Anda mendesain templat di kelas (misalnya, anggota kosong Note[]
yang perlu diisi, atau String name
bidang yang perlu diberi nilai), ingat bahwa tugas dari objek kelas ini untuk mengisi templat ( tugas atau mungkin mereka yang membuat objek ini). Subkelas dimaksudkan untuk menambah fungsionalitas, bukan untuk mengisi templat.
Anda mungkin tergoda untuk membuat semacam "superclass Person
, subclass, John
dan Mary
" sistem, seperti yang Anda lakukan, karena Anda menyukai formalitas yang Anda dapatkan.
Dengan cara ini, Anda bisa mengatakan Person p = new Mary()
, alih-alih Person p = new Person("Mary", 57, Sex.FEMALE)
. Itu membuat segalanya lebih teratur, dan lebih terstruktur. Tapi seperti yang kami katakan, membuat kelas baru untuk setiap kombinasi data bukanlah pendekatan yang baik, karena membengkokkan kode untuk apa-apa dan membatasi Anda dalam hal kemampuan runtime.
Jadi inilah solusinya: gunakan pabrik dasar, bahkan mungkin yang statis. Seperti itu:
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);
}
// ...
}
Dengan cara ini, Anda dapat dengan mudah menggunakan 'preset' yang 'datang dengan program', seperti:, Person mary = PersonFactory.createMary()
tetapi Anda juga berhak untuk merancang orang baru secara dinamis, misalnya dalam hal Anda ingin mengizinkan pengguna untuk melakukannya . Misalnya:
// .. 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);
Atau bahkan lebih baik: lakukan sesuatu seperti itu:
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;
}
}
Saya terbawa suasana. Saya pikir Anda mendapatkan idenya.
Subclass tidak dimaksudkan untuk mengisi template yang ditetapkan oleh superclasses mereka. Subkelas dimaksudkan untuk menambah fungsionalitas . Objek dimaksudkan untuk mengisi templat, itulah gunanya.
Anda seharusnya tidak membuat kelas baru untuk setiap kemungkinan kombinasi data. (Sama seperti saya seharusnya tidak membuat Scale
subclass baru untuk setiap kombinasi Note
s yang mungkin).
Ini adalah pedoman: setiap kali Anda membuat subkelas baru, pertimbangkan apakah itu menambahkan fungsionalitas baru yang tidak ada di superclass. Jika jawaban untuk pertanyaan itu adalah "tidak", daripada Anda mungkin mencoba untuk 'mengisi template' dari superclass, dalam hal ini buat saja sebuah objek. (Dan mungkin Pabrik dengan 'preset', untuk membuat hidup lebih mudah).
Semoga itu bisa membantu.