JavaRush /Blog Java /Random-PL /Przerwa kawowa #90. 4 filary programowania obiektowego

Przerwa kawowa #90. 4 filary programowania obiektowego

Opublikowano w grupie Random-PL
Źródło: The Geek Asian Przyjrzyjmy się czterem podstawom programowania obiektowego i spróbujmy zrozumieć, jak działają. Programowanie obiektowe (OOP) jest jednym z głównych paradygmatów programowania. Może to być łatwe i proste lub wręcz przeciwnie, bardzo złożone. Wszystko zależy od tego, jak zdecydujesz się rozwijać swoją aplikację. Przerwa kawowa #90.  4 filary programowania obiektowego - 1Istnieją 4 filary OOP:
  1. Kapsułkowanie.
  2. Dziedzictwo.
  3. Abstrakcja.
  4. Wielopostaciowość.
Omówimy teraz każdy z nich z krótkim wyjaśnieniem i prawdziwym przykładem kodu.

1. Hermetyzacja

Wszyscy badaliśmy enkapsulację jako ukrywanie elementów danych i umożliwianie użytkownikom dostępu do danych przy użyciu metod publicznych. Nazywamy je pobierającymi i ustawiającymi. Teraz zapomnijmy o tym i znajdźmy prostszą definicję. Hermetyzacja to metoda ograniczania użytkownikowi możliwości bezpośredniej zmiany elementów danych lub zmiennych klasy w celu zachowania integralności danych. Jak to zrobić? Ograniczamy dostęp do zmiennych, przełączając modyfikator dostępu na prywatny i udostępniając publiczne metody, za pomocą których można uzyskać dostęp do danych. Przyjrzyjmy się konkretnym przykładom poniżej. Pomoże nam to zrozumieć, w jaki sposób możemy wykorzystać enkapsulację do utrzymania integralności danych. Bez enkapsulacji:
/**
 * @author thegeekyasian.com
 */
public class Account {

  public double balance;

  public static void main(String[] args) {

  	Account theGeekyAsianAccount = new Account();

  	theGeekyAsianAccount.balance = -54;
  }
}
W powyższym fragmencie kodu metoda main() uzyskuje bezpośredni dostęp do zmiennej saldo . Dzięki temu użytkownik może ustawić dowolną podwójną wartość zmiennej salda klasy Konto . Możemy utracić integralność danych, pozwalając każdemu ustawić saldo na dowolną nieprawidłową liczbę, na przykład -54 w tym przypadku. Z enkapsulacją:
/**
 * @author thegeekyasian.com
 */
public class Account {

  private double balance;

  public void setBalance(double balance) {

    if(balance >= 0) { // Validating input data in order to maintain data integrity
	  this.balance = balance;
    }

    throw new IllegalArgumentException("Balance cannot be less than zero (0)");
  }

  public static void main(String[] args) {

  	Account theGeekyAsianAccount = new Account();

  	theGeekyAsianAccount.setBalance(1); // Valid input - Allowed
  	theGeekyAsianAccount.setBalance(-55); // Stops user and throws exception
  }
}
W tym kodzie ograniczyliśmy dostęp do zmiennej saldo i dodaliśmy metodę setBalance() , która pozwala użytkownikom ustawić wartość salda dla Account . Setter sprawdza podaną wartość przed przypisaniem jej do zmiennej. Jeśli wartość jest mniejsza od zera, zgłaszany jest wyjątek. Dzięki temu integralność danych nie zostanie naruszona. Mam nadzieję, że po wyjaśnieniu powyższych przykładów wartość enkapsulacji jako jednego z czterech filarów OOP jest jasna.

2. Dziedziczenie

Dziedziczenie to metoda uzyskiwania właściwości innej klasy, które mają wspólne cechy. Pozwala nam to zwiększyć możliwość ponownego użycia i ograniczyć powielanie kodu. Metoda opiera się również na zasadzie interakcji dziecko-rodzic, gdy element podrzędny dziedziczy właściwości swojego rodzica. Przyjrzyjmy się dwóm krótkim przykładom i zobaczmy, jak dziedziczenie czyni kod prostszym i łatwiejszym do ponownego użycia. Bez dziedziczenia:
/**
 * @author thegeekyasian
 */
public class Rectangle {

  private int width;
  private int height;

  public Rectangle(int width, int height) {
	this.width = width;
	this.height = height;
  }

  public int getArea() {
	return width * height;
  }
}

public class Square {

  private int width; // Duplicate property, also used in class Rectangle

  public Square(int width) {
	this.width = width;
  }

  public int getArea() { // Duplicate method, similar to the class Rectangle
	return this.width * this.width;
  }
}
Dwie podobne klasy mają wspólne właściwości szerokości i metodę getArea() . Możemy zwiększyć ponowne wykorzystanie kodu, dokonując niewielkiej refaktoryzacji, w której klasa Square ostatecznie dziedziczy z klasy Rectangle . Z dziedziczeniem:
/**
 * @author thegeekyasian
 */
public class Rectangle {

  private int width;
  private int height;

  public Rectangle(int width, int height) {
	this.width = width;
	this.height = height;
  }

  public int getArea() {
	return width * height;
  }
}

public class Square extends Rectangle {

  public Square(int width) {
	super(width, width); // A rectangle with the same height as width is a square
  }
}
Po prostu rozszerzając klasę Rectangle , otrzymujemy klasę Square jako typ Rectangle . Oznacza to, że dziedziczy wszystkie właściwości wspólne dla Square i Rectangle . W powyższych przykładach widzimy, jak dziedziczenie odgrywa ważną rolę w umożliwieniu ponownego wykorzystania kodu. Pozwala także klasie dziedziczyć zachowanie swojej klasy nadrzędnej.

3. Abstrakcja

Abstrakcja to technika przedstawiania użytkownikowi jedynie istotnych szczegółów poprzez ukrywanie niepotrzebnych lub nieistotnych szczegółów obiektu. Pomaga zmniejszyć złożoność operacyjną po stronie użytkownika. Abstrakcja pozwala nam zapewnić użytkownikowi prosty interfejs bez pytania o skomplikowane szczegóły w celu wykonania akcji. Mówiąc najprościej, daje użytkownikowi możliwość prowadzenia samochodu bez konieczności dokładnego zrozumienia działania silnika. Przyjrzyjmy się najpierw przykładowi, a następnie omówmy, w jaki sposób abstrakcja nam pomaga.
/**
* @author thegeekyasian.com
*/
public class Car {

  public void lock() {}
  public void unlock() {}

  public void startCar() {

	checkFuel();
	checkBattery();
	whatHappensWhenTheCarStarts();
  }

  private void checkFuel() {
	// Check fuel level
  }

  private void checkBattery() {
	// Check car battery
  }

  private void whatHappensWhenTheCarStarts() {
	// Magic happens here
  }
}
W powyższym kodzie metody lock() , unlock() i startCar() są publiczne, a pozostałe są prywatne dla klasy. Ułatwiliśmy użytkownikowi „prowadzenie samochodu”. Oczywiście mógłby ręcznie sprawdzić checkFuel() i checkBattery() przed uruchomieniem samochodu za pomocą startCar() , ale to tylko skomplikowałoby proces. W przypadku powyższego kodu wystarczy, że użytkownik użyje startCar() , a klasa zajmie się resztą. To właśnie nazywamy abstrakcją.

4. Polimorfizm

Ostatnim i najważniejszym z czterech filarów OOP jest polimorfizm. Polimorfizm oznacza „wiele form”. Jak sama nazwa wskazuje, jest to funkcja, która pozwala wykonać akcję na wiele lub różne sposoby. Kiedy mówimy o polimorfizmie, nie ma wiele do omówienia, chyba że mówimy o jego typach. Wyróżnia się dwa rodzaje polimorfizmu:
  1. Przeciążanie metod - polimorfizm statyczny (Static Binding).
  2. Nadpisywanie metod - polimorfizm dynamiczny (Dynamic Binding).
Omówmy każdy z tych typów i zobaczmy, jaka jest między nimi różnica.

Przeciążanie metody - polimorfizm statyczny:

Przeciążanie metod lub polimorfizm statyczny, znany również jako wiązanie statyczne lub wiązanie w czasie kompilacji, to typ, w którym wywołania metod są określane w czasie kompilacji. Przeciążanie metod pozwala nam mieć wiele metod o tej samej nazwie, mających różne typy danych parametrów, różną liczbę parametrów lub jedno i drugie. Pytanie jednak brzmi: dlaczego przeciążanie metod (lub statyczny polimorfizm) jest przydatne? Przyjrzyjmy się poniższym przykładom, aby lepiej zrozumieć przeciążanie metod. Bez przeciążania metod:
/**
* @author thegeekyasian.com
*/
public class Number {

  public void sumInt(int a, int b) {
	System.out.println("Sum: " + (a + b));
  }

  public void sumDouble(double a, double b) {
	System.out.println("Sum: " + (a + b));
  }

  public static void main(String[] args) {

	Number number = new Number();

	number.sumInt(1, 2);
	number.sumDouble(1.8, 2.5);
  }
}
W powyższym przykładzie stworzyliśmy dwie metody o różnych nazwach, aby dodać dwa różne typy liczb. Jeśli będziemy kontynuować podobną implementację, będziemy mieli wiele metod o różnych nazwach. Obniży to jakość i dostępność kodu. Aby to poprawić, możemy zastosować przeciążanie metod, używając tej samej nazwy dla różnych metod. Dzięki temu użytkownik będzie miał jedną opcję jako punkt wejścia do sumowania różnych typów liczb. Przeciążanie metod działa, gdy dwie lub więcej metod ma tę samą nazwę, ale różne parametry. Typ zwrotu może być taki sam lub inny. Ale jeśli dwie metody mają tę samą nazwę, te same parametry, ale różne typy zwracanych wartości, spowoduje to przeciążenie i błąd kompilacji! Z przeciążeniem metody:
/**
* @author thegeekyasian.com
*/
public class Number {

  public void sum(int a, int b) {
	System.out.println("Sum: " + (a + b));
  }

  public void sum(double a, double b) {
	System.out.println("Sum: " + (a + b));
  }

  public static void main(String[] args) {

	Number number = new Number();

	number.sum(1, 2);
	number.sum(1.8, 2.5);
  }
}
W tym samym kodzie, z kilkoma drobnymi zmianami, udało nam się przeciążyć obie metody, dzięki czemu nazwy obu metod były takie same. Użytkownik może teraz określić swoje specyficzne typy danych jako parametry metody. Następnie wykona akcję w oparciu o podany typ danych. To powiązanie metody jest wykonywane w czasie kompilacji, ponieważ kompilator wie, która metoda zostanie wywołana z określonym typem parametru. Dlatego nazywamy to wiązaniem w czasie kompilacji.

Nadpisywanie metod - polimorfizm dynamiczny:

W przeciwieństwie do przeciążania metod, zastępowanie metod pozwala uzyskać dokładnie taki sam podpis, jak wiele metod, ale muszą one należeć do wielu różnych klas. Pytanie brzmi, co jest w tym takiego wyjątkowego? Klasy te mają relację IS-A, co oznacza, że ​​muszą dziedziczyć po sobie. Innymi słowy, w przypadku nadpisywania metod lub polimorfizmu dynamicznego metody są przetwarzane dynamicznie w czasie wykonywania, gdy metoda jest wywoływana. Odbywa się to w oparciu o odwołanie do obiektu, z którym jest inicjowany. Oto mały przykład przesłaniania metody:
/**
* @author thegeekyasian.com
*/
public class Animal {

  public void walk() {
	System.out.println("Animal walks");
  }
}

public class Cat extends Animal {

  @Override
  public void walk() {
	System.out.println("Cat walks");
  }
}

public class Dog extends Animal {

  @Override
  public void walk() {
	System.out.println("Dog walks");
  }
}

public class Main {

  public static void main(String[] args) {

	Animal animal = new Animal();
	animal.walk(); // Animal walks

	Cat cat = new Cat();
	cat.walk(); // Cat walks

	Dog dog = new Dog();
	dog.walk(); // Dog walks

	Animal animalCat = new Cat(); // Dynamic Polymorphism
	animalCat.walk(); // Cat walks

	Animal animalDog = new Dog(); // Dynamic Polymorphism
	animalDog.walk(); //Dog walks
  }
}
W tym nadrzędnym przykładzie dynamicznie przypisaliśmy obiekty typu „Pies” i „Kot” do typu „Zwierzę”. Dzięki temu możemy dynamicznie wywoływać metodę walk() na instancjach, do których się odwołujemy, w czasie wykonywania. Możemy to zrobić za pomocą nadpisywania metod (lub dynamicznego polimorfizmu). Na tym kończy się nasza krótka dyskusja na temat czterech filarów OOP i mam nadzieję, że uznasz ją za przydatną.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION