Buku "Pendekatan Berorientasi Objek. Int 5. ed. "

gambarPemrograman Berorientasi Objek (OOP) adalah jantung dari bahasa C ++, Java, C #, Visual Basic .NET, Ruby, Objective-C, dan bahkan bahasa Swift. Mereka tidak dapat melakukannya tanpa objek teknologi web, karena mereka menggunakan JavaScript, Python dan PHP.

Itulah sebabnya Matt Weisfeld menyarankan untuk mengembangkan pemikiran berorientasi objek dan hanya kemudian melanjutkan dengan pengembangan berorientasi objek dalam bahasa pemrograman tertentu.

Buku ini ditulis oleh pengembang untuk pengembang dan memungkinkan Anda memilih pendekatan terbaik untuk menyelesaikan masalah tertentu. Anda akan belajar cara menerapkan pewarisan dan komposisi dengan benar, memahami perbedaan antara agregasi dan asosiasi, dan berhenti membingungkan antarmuka dan implementasi.

Teknologi pemrograman terus berubah dan berkembang, tetapi konsep berorientasi objek adalah platform independen dan tetap efektif secara konsisten. Publikasi ini berfokus pada fondasi dasar OOP: pola desain, dependensi, dan prinsip-prinsip SOLID yang akan membuat kode Anda dimengerti, fleksibel, dan dipelihara dengan baik.

Prinsip Desain Berorientasi Objek SOLID


1. SRP: prinsip tanggung jawab tunggal


Prinsip tanggung jawab tunggal menyatakan bahwa hanya satu alasan yang diperlukan untuk membuat perubahan pada kelas. Setiap modul kelas dan program harus memiliki satu tugas dalam prioritas. Oleh karena itu, Anda tidak boleh memperkenalkan metode yang dapat menyebabkan perubahan pada kelas karena lebih dari satu alasan. Jika deskripsi kelas berisi kata "dan", maka prinsip SRP mungkin dilanggar. Dengan kata lain, setiap modul atau kelas harus bertanggung jawab atas satu bagian dari fungsionalitas perangkat lunak, dan tanggung jawab tersebut harus dienkapsulasi sepenuhnya di dalam kelas.

Membuat hierarki angka adalah salah satu contoh klasik yang menggambarkan warisan. Contoh ini sering ditemukan dalam pengajaran, dan saya menggunakannya di sepanjang bab ini (dan juga di seluruh buku). Dalam contoh ini, kelas Circle mewarisi atribut dari kelas Bentuk. Kelas Shape menyediakan metode calcArea abstrak () sebagai kontrak untuk subclass. Setiap kelas yang mewarisi dari Shape harus memiliki implementasi sendiri dari metode calcArea:

abstract class Shape{
     protected String name;
     protected double area;
     public abstract double calcArea();
}

Dalam contoh ini, kelas Lingkaran, yang mewarisi dari kelas Bentuk, menyediakan penerapan metode calcArea (), jika perlu:

class Circle extends Shape{
     private double radius;

     public Circle(double r) {
           radius = r;
     }
     public double calcArea() {
           area = 3.14*(radius*radius) ;
           return (area);
     };
}

Kelas ketiga, CalculateAreas, menghitung luas berbagai bentuk yang terdapat dalam array Bentuk. Bentuk array tidak terbatas dalam ukuran dan dapat berisi berbagai bentuk, seperti kotak dan segitiga.

class CalculateAreas {
     Shape[] shapes;
     double sumTotal=0;
     public CalculateAreas(Shape[] sh) {
           this.shapes = sh;
     }
     public double sumAreas() {
           sumTotal=0;
           for (inti=0; i<shapes.length; i++) {
           sumTotal = sumTotal + shapes[i].calcArea() ;
           }
           return sumTotal ;
     }
     public void output() {
           System.out.printIn("Total of all areas = " + sumTotal);
     }
}

Perhatikan bahwa kelas CalculateAreas juga menangani keluaran aplikasi, yang dapat menyebabkan masalah. Perilaku penghitungan area dan perilaku output terkait karena mereka terkandung dalam kelas yang sama.

Kami dapat memverifikasi fungsionalitas kode ini menggunakan aplikasi uji TestShape yang sesuai:

public class TestShape {
      public static void main(String args[]) {

            System.out.printin("Hello World!");

            Circle circle = new Circle(1);

            Shape[] shapeArray = new Shape[1];
            shapeArray[0] = circle;

            CalculateAreas ca = new CalculateAreas(shapeArray) ;

            ca.sumAreas() ;
            ca.output();
      }
}

Sekarang kami memiliki aplikasi pengujian yang kami miliki, kami dapat fokus pada masalah prinsip tanggung jawab tunggal. Sekali lagi, masalahnya adalah dengan kelas CalculateAreas dan fakta bahwa kelas ini berisi perilaku untuk menjumlahkan bidang berbagai bentuk, serta untuk menghasilkan data.

Pertanyaan mendasar (dan, sebenarnya, masalahnya) adalah bahwa jika Anda perlu mengubah fungsionalitas metode output (), Anda perlu membuat perubahan pada kelas CalculateAreas terlepas dari apakah metode untuk menghitung area perubahan bentuk. Sebagai contoh, jika kita tiba-tiba ingin menampilkan data ke konsol HTML, dan tidak ke teks biasa, kita perlu mengkompilasi ulang dan menyematkan kembali kode, yang menambahkan bidang gambar. Semua karena tanggung jawab terkait.

Sesuai dengan prinsip tanggung jawab tunggal, tugasnya adalah mengubah satu metode tidak mempengaruhi metode lain dan tidak harus mengkompilasi ulang. "Kelas harus memiliki satu, hanya satu, alasan untuk perubahan - satu-satunya tanggung jawab yang perlu diubah."

Untuk mengatasi masalah ini, Anda bisa meletakkan dua metode dalam kelas yang terpisah, satu untuk output konsol asli, yang lain untuk output HTML:

class CaiculateAreas {;
     Shape[] shapes;
     double sumTotal=0;

     public CalculateAreas(Shape[] sh) {
           this.shapes = sh;
     }

     public double sumAreas() {
           sumTotal=0;

           for (inti=0; i<shapes.length; i++) {

                sumTotal = sumTotal + shapes[i].calcArea();

           }

                return sumTotal;
           }
}
class OutputAreas {
     double areas=0;
     public OutputAreas (double a) {
           this.areas = a;
     }

           public void console() {
           System.out.printin("Total of all areas = " + areas);
     }
     public void HTML() {
           System.out.printIn("<HTML>") ;
           System.out.printin("Total of all areas = " + areas);
           System.out.printin("</HTML>") ;
     }
}

Intinya di sini adalah bahwa sekarang Anda dapat mengirim kesimpulan ke arah yang berbeda tergantung pada kebutuhan. Jika Anda ingin menambahkan kemungkinan metode output lain, misalnya, JSON, Anda bisa menambahkannya ke kelas OutputAreas tanpa harus membuat perubahan ke kelas CalculateAreas. Sebagai hasilnya, Anda dapat mendistribusikan kembali kelas CalculateAreas tanpa memengaruhi kelas lain dengan cara apa pun.

2. OCP: prinsip buka / tutup


Prinsip keterbukaan / kedekatan menyatakan bahwa Anda dapat memperluas perilaku kelas tanpa membuat perubahan.

Mari kita perhatikan lagi contoh dengan angka. Dalam kode di bawah ini, ada kelas ShapeCalculator yang mengambil objek Rectangle, menghitung area objek ini, dan mengembalikan nilai. Ini adalah aplikasi sederhana, tetapi hanya bekerja dengan persegi panjang.

class Rectangle{
     protected double length;
     protected double width;

     public Rectangle(double 1, double w) {
           length = 1;
           width = w;
     };
}
class CalculateAreas {
     private double area;

     public double calcArea(Rectangle r) {

           area = r.length * r.width;

           return area;
     }
}
public class OpenClosed {
      public static void main(String args[]) {

            System.out.printin("Hello World");

            Rectangle r = new Rectangle(1,2);

            CalculateAreas ca = new CalculateAreas ();

            System.out.printin("Area = "+ ca.calcArea(r));
      }
}

Fakta bahwa aplikasi ini hanya berfungsi dalam kasus persegi panjang mengarah pada batasan yang dengan jelas menjelaskan prinsip keterbukaan / penutupan: jika kita ingin menambahkan kelas Circle ke kelas CalculateArea (ubah apa yang dilakukannya), kita perlu membuat perubahan pada modul itu sendiri. Jelas, ini bertentangan dengan prinsip keterbukaan / kedekatan, yang menyatakan bahwa kita tidak boleh membuat perubahan pada modul untuk mengubah apa yang dilakukannya.

Untuk mematuhi prinsip keterbukaan / kedekatan, kita dapat kembali ke contoh yang sudah diuji dengan angka, di mana kelas Shape abstrak dibuat dan langsung angka yang diwarisi dari kelas Shape, yang memiliki metode getArea abstrak ().

Saat ini, Anda dapat menambahkan sebanyak mungkin kelas yang berbeda, tanpa perlu membuat perubahan langsung ke kelas Bentuk (misalnya, kelas Lingkaran). Sekarang kita dapat mengatakan bahwa kelas Shape ditutup.

Kode di bawah ini menyediakan implementasi solusi untuk persegi panjang dan lingkaran dan memungkinkan Anda untuk membuat jumlah bentuk yang tidak terbatas:

abstract class Shape {
      public abstract double getArea() ;
}
class Rectangle extends Shape
{

      protected double length;
      protected double width;

      public Rectangle(double 1, double w) {
            length = 1;
            width = w;
      };
      public double getArea() {
            return length*width;
      }

}
class Circle extends Shape
{
      protected double radius;

      public Circle(double r) {
            radius = r;
      };
      public double getArea() {
            return radius*radius*3.14;
      }
}
class CalculateAreas {
      private double area;

      public double calcArea(Shape s) {
            area = s.getArea();
            return area;
      }
}

public class OpenClosed {
      public static void main(String args[]) {

            System.out.printiIn("Hello World") ;

            CalculateAreas ca = new CalculateAreas() ;

            Rectangle r = new Rectangle(1,2);

            System.out.printIn("Area = " + ca.calcArea(r));

            Circle c = new Circle(3);

            System.out.printIn("Area = " + ca.calcArea(c));
}
}

Perlu dicatat bahwa dengan implementasi ini, metode CalculateAreas () tidak boleh dimodifikasi ketika membuat instance baru dari kelas Shape.

Anda dapat mengubah skala kode tanpa khawatir tentang keberadaan kode sebelumnya. Prinsip keterbukaan / kedekatan adalah Anda harus memperluas kode menggunakan subclass sehingga kelas asli tidak memerlukan pengeditan. Namun, konsep "ekstensi" itu sendiri kontroversial dalam beberapa diskusi mengenai prinsip-prinsip SOLID. Sederhananya, jika kita lebih suka komposisi daripada warisan, bagaimana ini mempengaruhi prinsip keterbukaan / kedekatan?

Saat mematuhi salah satu prinsip SOLID, kode dapat memenuhi kriteria prinsip SOLID lainnya. Misalnya, ketika merancang sesuai dengan prinsip keterbukaan / kedekatan, kode mungkin cocok untuk prinsip tanggung jawab tunggal.

3. LSP: Prinsip substitusi Lisk


Menurut prinsip substitusi Liskov, desain harus menyediakan kemungkinan untuk mengganti instance instance dari kelas induk dengan instance dari salah satu kelas anak. Jika kelas induk dapat melakukan tugas apa pun, kelas anak juga harus bisa.

Pertimbangkan beberapa kode yang benar pada pandangan pertama, tetapi melanggar prinsip substitusi Lisk. Kode di bawah ini berisi kelas abstrak Bentuk generik. Kelas Rectangle, pada gilirannya, mewarisi atribut dari kelas Shape dan menimpa metode calcArea abstraknya. Kelas Square, pada gilirannya, mewarisi dari Rectangle.

abstract class Shape{
      protected double area;

      public abstract double calcArea();
}
class Rectangle extends Shape{
      private double length;
      private double width;

      public Rectangle(double 1, double w) {
            length = 1;
            width = w;
      }
      public double calcArea() {
            area = length*width;
            return (area) ;
      };
}
class Square extends Rectangle{
      public Square(double s) {
            super(s, Ss);
      }
}

public class LiskovSubstitution {
      public static void main(String args[]) {

            System.out.printIn("Hello World") ;

            Rectangle r = new Rectangle(1,2);

            System.out.printin("Area = " + r.calcArea());

            Square s = new Square(2) ;

            System.out.printin("Area = " + s.calcArea());
      }
}

Sejauh ini, sangat bagus: persegi panjang adalah turunan dari gambar, jadi tidak ada yang perlu dikhawatirkan, karena alun-alun adalah turunan dari persegi panjang - dan sekali lagi, semuanya benar, kan?

Sekarang mari kita ajukan pertanyaan filosofis: apakah persegi masih persegi panjang? Banyak yang akan menjawab dengan tegas. Meskipun dapat diasumsikan bahwa persegi adalah kasus khusus persegi panjang, propertinya akan berbeda. Persegi panjang adalah jajar genjang (sisi yang berlawanan adalah sama), seperti persegi. Pada saat yang sama, persegi juga merupakan belah ketupat (semua sisi adalah sama), sedangkan persegi panjang tidak. Karena itu, ada perbedaan.

Ketika datang ke desain berorientasi objek, masalahnya bukan geometri. Masalahnya adalah bagaimana tepatnya kita membuat persegi panjang dan kotak. Berikut adalah konstruktor untuk kelas Rectangle:

public Rectangle(double 1, double w) {
      length = 1;
      width = w;
}

Jelas, konstruktor membutuhkan dua parameter. Namun, konstruktor untuk kelas Square hanya membutuhkan satu, meskipun kelas induk, Rectangle, membutuhkan dua.

class Square extends Rectangle{
      public Square(double s) {
      super(s, Ss);
}

Bahkan, fungsional untuk menghitung area sedikit berbeda dalam kasus masing-masing dari dua kelas ini. Yaitu, kelas Square, seolah-olah, meniru Rectangle, melewati parameter yang sama dua kali ke konstruktor. Tampaknya solusi semacam itu sangat cocok, tetapi sebenarnya itu dapat menyesatkan pengembang yang menyertai kode, yang cukup penuh dengan jebakan ketika disertai di masa depan. Setidaknya ini adalah masalah dan, mungkin, keputusan desain yang meragukan. Ketika satu konstruktor memanggil yang lain, itu ide yang baik untuk istirahat dan mempertimbangkan kembali konstruk - mungkin kelas anak tidak dibangun dengan benar.

Bagaimana menemukan jalan keluar dari situasi ini? Sederhananya, Anda tidak bisa mengganti kelas Kotak untuk Persegi Panjang. Dengan demikian, Square tidak harus menjadi anak dari kelas Rectangle. Mereka harus kelas yang terpisah.

abstract class Shape {
      protected double area;

      public abstract double calcArea();
}

class Rectangle extends Shape {

      private double length;
      private double width;

      public Rectangle(double 1, double w) {
            length = 1;
            width = w;
      }

      public double calcArea() {
            area = length*width;
            return (area);
      };
}

class Square extends Shape {
      private double side;

      public Square(double s) {
            side = s;
      }
      public double calcArea() {
            area = side*side;
            return (area);
      };
}

public class LiskovSubstitution {
      public static void main(String args[]) {

             System.out.printIn("Hello World") ;

             Rectangle r = new Rectangle(1,2);

             System.out.printIn("Area = " + r.calcArea());

             Square s = new Square(2) ;

             System.out.printIn("Area = " + s.calcArea());
      }
}

4. ISP: prinsip berbagi antarmuka


Prinsip pemisahan antarmuka menyatakan bahwa lebih baik membuat banyak antarmuka kecil daripada beberapa antarmuka besar.

Dalam contoh ini, kami membuat antarmuka tunggal yang mencakup beberapa perilaku untuk kelas Mamalia, yaitu eat () dan makeNoise ():

interface IMammal {
     public void eat();
     public void makeNoise() ;
}
class Dog implements IMammal {
     public void eat() {
           System.out.printIn("Dog is eating");
     }
     public void makeNoise() {
           System.out.printIn("Dog is making noise");
     }
}
public class MyClass {
      public static void main(String args[]) {

            System.out.printIn("Hello World");

            Dog fido = new Dog();
            fido.eat();
            fido.makeNoise()
      }
}

Alih-alih membuat antarmuka tunggal untuk kelas Mamalia, Anda perlu membuat
antarmuka terpisah untuk semua perilaku:

interface IEat {
     public void eat();
}
interface IMakeNoise {
     public void makeNoise() ;
}
class Dog implements IEat, IMakeNoise {
     public void eat() {
           System.out.printIn("Dog is eating");
     }
     public void makeNoise() {
           System.out.printIn("Dog is making noise");
     }
}
public class MyClass {
      public static void main(String args[]) {

            System.out.printIn("Hello World") ;

            Dog fido = new Dog();
            fido.eat();
            fido.makeNoise();
      }
}

Kami memisahkan perilaku dari kelas Mamalia. Ternyata alih-alih membuat satu kelas Mamalia melalui pewarisan (lebih tepatnya, antarmuka), kita beralih ke desain berdasarkan komposisi, mirip dengan strategi yang digunakan pada bab sebelumnya.

Dalam beberapa kata, dengan pendekatan ini, kita dapat membuat contoh kelas Mamalia menggunakan komposisi, daripada dipaksa untuk menggunakan perilaku yang tertanam dalam satu kelas Mamalia. Sebagai contoh, misalkan mamalia ditemukan yang tidak makan, tetapi menyerap nutrisi melalui kulit. Jika kita mewarisi dari kelas mamalia yang mengandung perilaku eat (), perilaku ini akan menjadi mubal bagi mamalia baru. Selain itu, jika semua perilaku diletakkan dalam antarmuka tunggal yang terpisah, itu akan menghasilkan kelas masing-masing mamalia persis seperti yang dimaksudkan.

»Informasi lebih lanjut tentang buku ini dapat ditemukan di situs web penerbit
» Daftar Isi
» Kutipan

untuk Habrozhitelami Diskon 25% pada kupon -OOP

Setelah pembayaran versi kertas buku, sebuah buku elektronik dikirim melalui email.

Source: https://habr.com/ru/post/undefined/


All Articles