출처: The Geek Asian 객체 지향 프로그래밍의 네 가지 기본 사항을 살펴보고 작동 방식을 이해해 보겠습니다. 객체 지향 프로그래밍(OOP)은 주요 프로그래밍 패러다임 중 하나입니다. 쉽고 간단할 수도 있고, 반대로 매우 복잡할 수도 있습니다. 그것은 모두 애플리케이션 개발 방법에 따라 달라집니다. OOP에는 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 유형 으로 얻을 수 있습니다 . 이는 Square 및 Rectangle 에 공통적인 모든 속성을 상속한다는 의미입니다 . 위의 예에서 상속이 코드를 재사용 가능하게 만드는 데 어떻게 중요한 역할을 하는지 알 수 있습니다. 또한 클래스가 상위 클래스의 동작을 상속할 수도 있습니다.
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의 네 가지 기둥 중 마지막이자 가장 중요한 것은 다형성입니다. 다형성은 “다양한 형태”를 의미합니다. 이름에서 알 수 있듯이 여러 가지 방법으로 작업을 수행할 수 있는 기능입니다. 다형성에 대해 이야기할 때 그 유형에 대해 이야기하지 않으면 논의할 것이 많지 않습니다. 다형성에는 두 가지 유형이 있습니다.- 메소드 오버로딩 - 정적 다형성(정적 바인딩).
- 메소드 재정의 - 동적 다형성(동적 바인딩).
메소드 오버로딩 - 정적 다형성:
정적 바인딩 또는 컴파일 타임 바인딩이라고도 하는 메서드 오버로딩 또는 정적 다형성은 메서드 호출이 컴파일 타임에 결정되는 유형입니다. 메소드 오버로딩을 사용하면 이름이 동일하거나 매개변수 데이터 유형이 다르거나 매개변수 수가 다르거나 둘 다를 갖는 여러 메소드를 가질 수 있습니다. 하지만 문제는 메소드 오버로딩(또는 정적 다형성)이 왜 유용한가입니다. 메소드 오버로딩을 더 잘 이해하기 위해 아래 예제를 살펴보겠습니다. 메소드 오버로딩 없이:/**
* @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의 네 가지 핵심 요소에 대한 간략한 논의를 마치겠습니다. 이 내용이 도움이 되기를 바랍니다.
GO TO FULL VERSION