JavaRush /Java Blog /Random-KO /커피 브레이크 #90. 객체지향 프로그래밍의 4가지 핵심

커피 브레이크 #90. 객체지향 프로그래밍의 4가지 핵심

Random-KO 그룹에 게시되었습니다
출처: The Geek Asian 객체 지향 프로그래밍의 네 가지 기본 사항을 살펴보고 작동 방식을 이해해 보겠습니다. 객체 지향 프로그래밍(OOP)은 주요 프로그래밍 패러다임 중 하나입니다. 쉽고 간단할 수도 있고, 반대로 매우 복잡할 수도 있습니다. 그것은 모두 애플리케이션 개발 방법에 따라 달라집니다. 커피 브레이크 #90.  객체지향 프로그래밍의 4가지 기둥 - 1OOP에는 4가지 기둥이 있습니다.
  1. 캡슐화.
  2. 계승.
  3. 추출.
  4. 다형성.
이제 간단한 설명과 실제 코드 예제를 통해 각각에 대해 논의하겠습니다.

1. 캡슐화

우리 모두는 데이터 요소를 숨기고 사용자가 공개 메서드를 사용하여 데이터에 액세스할 수 있도록 하는 캡슐화를 연구했습니다. 우리는 이것을 getter와 setter라고 부릅니다. 이제 이것에 대해서는 잊어버리고 더 간단한 정의를 찾아보겠습니다. 캡슐화는 데이터 무결성을 유지하기 위해 사용자가 데이터 멤버나 클래스 변수를 직접 변경하지 못하도록 제한하는 방법입니다. 어떻게 해야 할까요? 액세스 한정자를 비공개 로 전환 하고 데이터에 액세스하는 데 사용할 수 있는 공개 메서드를 노출하여 변수에 대한 액세스를 제한합니다. 아래에서 구체적인 예를 살펴보겠습니다. 이는 캡슐화를 사용하여 데이터 무결성을 유지하는 방법을 이해하는 데 도움이 됩니다. 캡슐화하지 않은 경우:
/**
 * @author thegeekyasian.com
 */
public class Account {

  public double balance;

  public static void main(String[] args) {

  	Account theGeekyAsianAccount = new Account();

  	theGeekyAsianAccount.balance = -54;
  }
}
위의 코드 조각에서 main() 메서드는 잔액 변수에 직접 액세스합니다 . 이를 통해 사용자는 Account 클래스 의 잔액 변수 에 이중 값을 설정할 수 있습니다 . 이 경우 -54와 같이 누구든지 잘못된 숫자 잔액을 설정하도록 허용하면 데이터 무결성이 손실될 수 있습니다 . 캡슐화 사용:
/**
 * @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
  }
}
이 코드에서는 잔액 변수에 대한 액세스를 제한 하고 사용자가 Account 에 대한 잔액 값을 설정할 수 있도록 하는 setBalance() 메서드를 추가했습니다 . setter는 제공된 값을 변수에 할당하기 전에 확인합니다. 값이 0보다 작으면 예외가 발생합니다. 이는 데이터의 무결성이 손상되지 않도록 보장합니다. 위의 예를 설명한 후 OOP의 네 가지 기둥 중 하나인 캡슐화의 가치가 명확해지기를 바랍니다.

2. 상속

상속은 공통 기능을 공유하는 다른 클래스의 속성을 얻는 방법입니다. 이를 통해 재사용성을 높이고 코드 중복을 줄일 수 있습니다. 또한 이 메서드에는 하위 요소가 상위 요소의 속성을 상속하는 경우 하위-부모 상호 작용의 원칙이 있습니다. 두 가지 간단한 예를 살펴보고 상속이 어떻게 코드를 더 단순하고 재사용 가능하게 만드는지 살펴보겠습니다. 상속 없이:
/**
 * @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;
  }
}
두 개의 유사한 클래스는 너비 속성 과 getArea() 메서드를 공유합니다 . Square 클래스가 Rectangle 클래스 에서 상속되는 부분 에 대한 약간의 리팩토링을 수행하여 코드 재사용을 늘릴 수 있습니다 . 상속의 경우:
/**
 * @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
  }
}
단순히 Rectangle 클래스를 확장하면 Square 클래스를 Rectangle 유형 으로 얻을 수 있습니다 . 이는 SquareRectangle 에 공통적인 모든 속성을 상속한다는 의미입니다 . 위의 예에서 상속이 코드를 재사용 가능하게 만드는 데 어떻게 중요한 역할을 하는지 알 수 있습니다. 또한 클래스가 상위 클래스의 동작을 상속할 수도 있습니다.

3. 추상화

추상화란 객체의 불필요하거나 관련 없는 세부 사항을 숨겨 사용자에게 꼭 필요한 세부 사항만 제시하는 기술입니다. 이는 사용자 측의 운영 복잡성을 줄이는 데 도움이 됩니다. 추상화를 사용하면 작업을 수행하기 위해 복잡한 세부 정보를 묻지 않고도 사용자에게 간단한 인터페이스를 제공할 수 있습니다. 간단히 말해서, 사용자는 엔진 작동 방식을 정확히 이해하지 않고도 자동차를 운전할 수 있습니다. 먼저 예제를 살펴보고 추상화가 어떻게 우리에게 도움이 되는지 논의해 보겠습니다.
/**
* @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
  }
}
위 코드에서 lock() , Unlock()startCar() 메서드는 공개이고 나머지는 클래스 전용입니다. 우리는 사용자가 "자동차를 운전"하는 것을 더 쉽게 만들었습니다. 물론 startCar()를 사용하여 자동차의 시동을 걸기 전에 checkFuel()checkBattery()를 수동으로 확인할 수도 있지만 그렇게 하면 프로세스가 복잡해집니다. 위 코드에서 사용자가 해야 할 일은 startCar()를 사용하는 것뿐이며 나머지는 클래스가 처리합니다. 이것이 우리가 추상화라고 부르는 것입니다.

4. 다형성

OOP의 네 가지 기둥 중 마지막이자 가장 중요한 것은 다형성입니다. 다형성은 “다양한 형태”를 의미합니다. 이름에서 알 수 있듯이 여러 가지 방법으로 작업을 수행할 수 있는 기능입니다. 다형성에 대해 이야기할 때 그 유형에 대해 이야기하지 않으면 논의할 것이 많지 않습니다. 다형성에는 두 가지 유형이 있습니다.
  1. 메소드 오버로딩 - 정적 다형성(정적 바인딩).
  2. 메소드 재정의 - 동적 다형성(동적 바인딩).
각 유형에 대해 논의하고 이들 간의 차이점이 무엇인지 살펴보겠습니다.

메소드 오버로딩 - 정적 다형성:

정적 바인딩 또는 컴파일 타임 바인딩이라고도 하는 메서드 오버로딩 또는 정적 다형성은 메서드 호출이 컴파일 타임에 결정되는 유형입니다. 메소드 오버로딩을 사용하면 이름이 동일하거나 매개변수 데이터 유형이 다르거나 매개변수 수가 다르거나 둘 다를 갖는 여러 메소드를 가질 수 있습니다. 하지만 문제는 메소드 오버로딩(또는 정적 다형성)이 왜 유용한가입니다. 메소드 오버로딩을 더 잘 이해하기 위해 아래 예제를 살펴보겠습니다. 메소드 오버로딩 없이:
/**
* @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);
  }
}
위의 예에서는 서로 다른 두 가지 유형의 숫자를 추가하기 위해 서로 다른 이름을 가진 두 가지 메서드를 만들었습니다. 유사한 구현을 계속하면 이름이 다른 여러 메서드가 있게 됩니다. 이렇게 하면 코드의 품질과 가용성이 저하됩니다. 이를 개선하기 위해 서로 다른 메소드에 동일한 이름을 사용하여 메소드 오버로딩을 사용할 수 있습니다. 이를 통해 사용자는 다양한 유형의 숫자를 합산하기 위한 진입점으로 하나의 옵션을 가질 수 있습니다. 메소드 오버로딩은 두 개 이상의 메소드가 이름은 같지만 매개변수가 다를 때 작동합니다. 반환 유형은 같을 수도 있고 다를 수도 있습니다. 그러나 두 메서드가 이름과 매개변수가 동일하지만 반환 유형이 다른 경우 오버로드와 컴파일 오류가 발생합니다! 메소드 오버로딩의 경우:
/**
* @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);
  }
}
동일한 코드에서 몇 가지 사소한 변경을 통해 두 메서드를 모두 오버로드하여 두 메서드의 이름을 동일하게 만들 수 있었습니다. 이제 사용자는 특정 데이터 유형을 메소드 매개변수로 지정할 수 있습니다. 그런 다음 제공된 데이터 유형에 따라 작업을 수행합니다. 이 메소드 바인딩은 컴파일러가 지정된 매개변수 유형으로 호출될 메소드를 알고 있기 때문에 컴파일 타임에 수행됩니다. 이것이 우리가 이를 컴파일 타임 바인딩이라고 부르는 이유입니다.

메소드 재정의 - 동적 다형성:

메서드 오버로딩과 달리 메서드 재정의를 사용하면 여러 메서드와 정확히 동일한 시그니처를 가질 수 있지만 여러 다른 클래스에 있어야 합니다. 문제는 무엇이 그렇게 특별한가 하는 것입니다. 이러한 클래스는 IS-A 관계를 갖습니다. 즉, 서로 상속해야 합니다. 즉, 메서드 재정의 또는 동적 다형성에서는 메서드가 호출될 때 런타임에 메서드가 동적으로 처리됩니다. 이는 초기화되는 개체에 대한 참조를 기반으로 수행됩니다. 다음은 메서드 재정의의 간단한 예입니다.
/**
* @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
  }
}
이 재정의 예제에서는 "Dog" 및 "Cat" 유형의 개체를 "Animal" 유형에 동적으로 할당했습니다. 이를 통해 런타임에 참조된 인스턴스에서 walk() 메서드를 동적 으로 호출할 수 있습니다 . 메서드 재정의(또는 동적 다형성)를 사용하여 이를 수행할 수 있습니다. 이것으로 OOP의 네 가지 핵심 요소에 대한 간략한 논의를 마치겠습니다. 이 내용이 도움이 되기를 바랍니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION