CodeGym /Java Blog /무작위의 /OOP의 원리
John Squirrels
레벨 41
San Francisco

OOP의 원리

무작위의 그룹에 게시되었습니다
자바는 객체 지향 언어입니다. 이는 객체 지향 패러다임을 사용하여 Java 프로그램을 작성해야 함을 의미합니다. 그리고 이 패러다임은 프로그램에서 개체와 클래스를 사용하는 것을 수반합니다. 예제를 사용하여 클래스와 개체가 무엇인지 이해하고 기본 OOP 원칙(추상화, 상속, 다형성 및 캡슐화)을 실제로 적용하는 방법을 살펴보겠습니다.

객체란 무엇입니까?

우리가 사는 세상은 사물로 이루어져 있습니다. 주위를 둘러보면 우리는 집, 나무, 자동차, 가구, 그릇, 컴퓨터로 둘러싸여 있음을 알 수 있습니다. 이 모든 것들은 객체이며, 각각은 일련의 특정 특성, 동작 및 목적을 가지고 있습니다. 우리는 사물에 익숙하며 항상 매우 특정한 목적을 위해 사물을 사용합니다. 예를 들어 직장에 가야 한다면 자동차를 이용합니다. 우리가 먹고 싶다면 접시를 사용합니다. 그리고 쉬고 싶다면 편안한 소파를 찾습니다. 인간은 일상 생활에서 문제를 해결하기 위해 사물의 관점에서 생각하는 데 익숙합니다. 이것이 객체가 프로그래밍에 사용되는 이유 중 하나입니다. 이 접근 방식을 객체 지향 프로그래밍이라고 합니다. 예를 들어 보겠습니다. 새 전화기를 개발했고 대량 생산을 시작하고 싶다고 상상해 보십시오. 휴대전화 개발자라면 용도가 무엇인지 알고 있을 것입니다. 기능 및 구성 요소(본체, 마이크, 스피커, 전선, 버튼 등). 게다가 이 부품들을 연결하는 방법은 당신만이 알고 있습니다. 하지만 전화기를 개인적으로 만들 계획은 없습니다. 전체 직원 팀이 이 작업을 수행해야 합니다. 전화기 부품 연결 방법을 반복적으로 설명할 필요가 없고 모든 전화기가 동일한 방식으로 제작되었는지 확인하려면 생산을 시작하기 전에 전화기가 어떻게 구성되어 있는지 설명하는 그림을 만들어야 합니다. OOP에서는 이러한 설명, 그림, 다이어그램 또는 템플릿을 클래스라고 합니다. 프로그램이 실행될 때 개체를 만드는 기초를 형성합니다. 클래스는 필드, 메소드 및 생성자로 구성된 공통 템플릿과 같은 특정 유형의 객체에 대한 설명입니다. 개체는 클래스의 인스턴스입니다.

추출

이제 현실 세계의 객체에서 프로그램의 객체로 어떻게 이동할 수 있는지 생각해 봅시다. 전화를 예로 들어 보겠습니다. 이 통신 수단은 100년 이상의 역사를 가지고 있습니다. 현대 전화기는 19세기 이전 전화기보다 훨씬 더 복잡한 장치입니다. 전화를 사용할 때 우리는 전화의 구성과 내부에서 발생하는 프로세스에 대해 생각하지 않습니다. 우리는 전화기 개발자가 제공하는 기능인 버튼이나 터치 스크린을 사용하여 전화번호를 입력하고 전화를 겁니다. 최초의 전화 인터페이스 중 하나는 전화를 걸기 위해 회전해야 하는 크랭크였습니다. 물론 이것은 그다지 편리하지 않았습니다. 그러나 완벽하게 기능을 수행했습니다. 가장 현대적인 전화기와 최초의 전화기를 비교하면 19세기 후반 장치와 최신 스마트폰에 가장 중요한 기능을 즉시 식별할 수 있습니다. 전화를 걸 수 있는 기능과 전화를 받을 수 있는 기능입니다. 사실, 이것이 전화를 전화로 만드는 것입니다. 다른 것이 아닙니다. 이제 객체의 가장 중요한 특성과 정보를 식별하는 OOP 원칙을 적용했습니다. 이 원리를 추상화라고 합니다. OOP에서 추상화는 실세계 작업의 요소를 프로그램의 객체로 표현하는 방법으로도 정의할 수 있습니다. 추상화는 항상 개체의 특정 속성의 일반화와 관련이 있으므로 가장 중요한 것은 당면한 작업의 맥락에서 의미 있는 정보와 중요하지 않은 정보를 분리하는 것입니다. 또한 여러 수준의 추상화가 있을 수 있습니다. 허락하다' 휴대폰에 추상화 원칙을 적용해 보십시오. 먼저 최초의 전화기부터 오늘날의 전화기까지 가장 일반적인 유형의 전화기를 식별할 것입니다. 예를 들어 그림 1의 다이어그램 형식으로 나타낼 수 있습니다. OOP의 원리 - 2추상화를 사용하여 우리는 이제 이 개체 계층 구조에서 일반적인 추상 개체(전화), 전화의 공통 특성(예: 생성 연도) 및 공통 인터페이스(모든 전화가 전화를 걸고 받을 수 있음)와 같은 일반 정보를 식별할 수 있습니다. 자바에서는 다음과 같이 표시됩니다.

public abstract class AbstractPhone {
    private int year;

    public AbstractPhone(int year) {
        this.year = year;
    }
    public abstract void call(int outgoingNumber);
    public abstract void ring(int incomingNumber);
}
프로그램에서 우리는 이 추상 클래스를 사용하고 아래에서 살펴볼 OOP의 다른 기본 원칙을 적용하여 새로운 유형의 전화기를 만들 수 있습니다.

캡슐화

추상화를 통해 우리는 모든 개체에 공통적인 것을 식별합니다. 그러나 각 종류의 전화기는 고유하며 다른 전화기와 다소 다릅니다. 프로그램에서 우리는 어떻게 경계를 긋고 이 개성을 식별합니까? 아무도 실수로 또는 고의로 전화기를 부수거나 한 모델을 다른 모델로 변환하려고 시도하지 못하도록 하려면 어떻게 해야 합니까? 현실 세계에서 대답은 분명합니다. 모든 부품을 휴대폰 케이스에 넣어야 합니다. 결국, 그렇게 하지 않으면(대신 전화기의 모든 내부 부품과 연결 전선을 외부에 남겨두는 경우) 일부 호기심 많은 실험가는 확실히 전화기를 "개선"하기를 원할 것입니다. 이러한 땜질을 방지하기 위해 객체의 설계 및 작동에 캡슐화 원칙이 사용됩니다. 이 원칙은 객체의 속성과 동작이 단일 클래스인 객체 ' 의 내부 구현은 사용자에게 숨겨져 있으며 개체 작업을 위한 공용 인터페이스가 제공됩니다. 프로그래머의 임무는 객체의 속성과 메서드 중 공개 액세스가 가능한 것과 액세스할 수 없는 내부 구현 세부 정보가 무엇인지 결정하는 것입니다.

캡슐화 및 액세스 제어

전화기를 만들 때 전화기에 대한 정보(생산 연도 또는 제조업체 로고)가 뒷면에 새겨져 있다고 가정합니다. 정보(해당 상태)는 이 특정 모델에 고유합니다. 제조업체가 이 정보를 변경할 수 없는지 확인했다고 말할 수 있습니다. 누군가 각인을 제거할 생각을 하지 않을 것입니다. Java 세계에서 클래스는 필드를 사용하여 미래 개체의 상태를 설명하고 해당 동작은 메서드를 사용하여 설명합니다. 개체의 상태 및 동작에 대한 액세스는 필드 및 메서드에 적용된 한정자(private, protected, public 및 default)를 사용하여 제어됩니다. 예를 들어 생산 연도, 제조업체 이름 및 메서드 중 하나는 클래스의 내부 구현 세부 정보이며 프로그램의 다른 개체에 의해 변경될 수 없다고 결정했습니다. 코드에서

public class SomePhone {

    private int year;
    private String company;
    public SomePhone(int year, String company) {
        this.year = year;
        this.company = company;
    }
private void openConnection(){
    // findSwitch
    // openNewConnection...
}
public void call() {
    openConnection();
    System.out.println("Calling");
}

public void ring() {
    System.out.println("Ring-ring");
}

 }
private 한정자는 클래스의 필드와 메서드가 이 클래스 내에서만 액세스되도록 허용합니다. 이는 private 메서드를 호출할 수 없기 때문에 외부에서 private 필드에 액세스할 수 없음을 의미합니다. openConnection 메서드 에 대한 액세스를 제한하면 해당 메서드가 다른 개체에서 사용되거나 작업을 방해하지 않도록 보장되므로 메서드의 내부 구현을 자유롭게 변경할 수 있습니다. 개체를 사용하기 위해 public 한정자를 사용하여 호출링 메서드를 사용할 수 있도록 둡니다. 객체 작업을 위한 공용 메서드를 제공하는 것도 캡슐화의 일부입니다. 액세스가 완전히 거부되면 무용지물이 되기 때문입니다.

계승

전화 다이어그램을 다시 살펴 보겠습니다. 모델이 분기를 따라 상위에 위치한 모델의 모든 기능을 가지고 있고 자신의 일부를 추가하는 계층 구조임을 알 수 있습니다. 예를 들어, 스마트폰은 통신을 위해 셀룰러 네트워크를 사용하고(휴대폰의 속성을 가짐), 무선 및 휴대가 가능하며(무선 전화기의 속성을 가짐), 전화를 걸고 받을 수 있습니다(전화기의 속성을 가짐). 여기에 있는 것은 개체 속성의 상속입니다. 프로그래밍에서 상속이란 기존 클래스를 사용하여 새 클래스를 정의하는 것을 의미합니다. 상속을 사용하여 스마트폰 클래스를 만드는 예를 살펴보겠습니다. 모든 무선 전화기는 일정한 배터리 수명을 가진 충전식 배터리로 전원을 공급받습니다. 따라서 이 속성을 무선 전화기 클래스에 추가합니다.

public abstract class CordlessPhone extends AbstractPhone {

    private int hour;

    public CordlessPhone (int year, int hour) {
        super(year);
        this.hour = hour;
    }
    }
휴대폰은 무선 전화기의 속성을 상속하며 이 클래스에서 callring 메서드를 구현합니다.

public class CellPhone extends CordlessPhone {
    public CellPhone(int year, int hour) {
        super(year, hour);
    }

    @Override
    public void call(int outgoingNumber) {
        System.out.println("Calling " + outgoingNumber);
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("Incoming call from " + incomingNumber);
    }
}
마지막으로 클래식 휴대폰과 달리 본격적인 운영 체제를 갖춘 스마트 폰 클래스가 있습니다. 운영 체제에서 실행할 수 있는 새 프로그램을 추가하여 스마트폰의 기능을 확장할 수 있습니다. 코드에서 클래스는 다음과 같이 설명할 수 있습니다.

public class Smartphone extends CellPhone {
    
    private String operationSystem;

    public Smartphone(int year, int hour, String operationSystem) {
        super(year, hour);
        this.operationSystem = operationSystem;
    }
public void install(String program) {
    System.out.println("Installing " + program + " for " + operationSystem);
}

}
보시다시피 우리는 Smartphone 클래스를 설명하기 위해 꽤 많은 새 코드를 만들었지만 새로운 기능을 가진 새 클래스를 얻었습니다. 이 OOP의 원칙은 필요한 Java 코드의 양을 크게 줄여 프로그래머의 삶을 더 쉽게 만듭니다.

다형성

다양한 종류의 전화의 모양과 디자인의 차이에도 불구하고 몇 가지 일반적인 동작을 식별할 수 있습니다. 모두 전화를 걸고 받을 수 있으며 모두 상당히 명확하고 간단한 컨트롤 세트를 가지고 있습니다. 프로그래밍 측면에서 추상화 원칙(우리가 이미 잘 알고 있는)을 통해 전화 개체에는 공통 인터페이스가 있다고 말할 수 있습니다. 그렇기 때문에 사람들은 장치의 기술적 세부 사항을 파고들지 않고도 동일한 컨트롤(기계식 버튼 또는 터치스크린)이 있는 다른 모델의 전화기를 쉽게 사용할 수 있습니다. 따라서 휴대폰을 계속 사용하고 친구의 유선 전화에서 쉽게 전화를 걸 수 있습니다. 프로그램이 객체의 내부 구조에 대한 정보 없이 공통 인터페이스로 객체를 사용할 수 있다는 OOP의 원리를 다형성이라고 합니다. 허락하다' 어떤 전화로도 다른 사용자에게 전화를 걸 수 있는 사용자를 설명하는 프로그램이 필요하다고 상상해 보십시오. 방법은 다음과 같습니다.

public class User {
    private String name;

    public User(String name) {
        this.name = name;
            }

    public void callAnotherUser(int number, AbstractPhone phone){
// And here's polymorphism: using the AbstractPhone type in the code!
        phone.call(number);
    }
}
 }
이제 여러 종류의 전화기에 대해 설명하겠습니다. 최초의 전화기 중 하나:

public class ThomasEdisonPhone extends AbstractPhone {

public ThomasEdisonPhone(int year) {
    super(year);
}
    @Override
    public void call(int outgoingNumber) {
        System.out.println("Crank the handle");
        System.out.println("What number would you like to connect to?");
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("The phone is ringing");
    }
}
일반 유선 전화:

public class Phone extends AbstractPhone {

    public Phone(int year) {
        super(year);
    }

    @Override
    public void call(int outgoingNumber) {
        System.out.println("Calling " + outgoingNumber);
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("The phone is ringing");
    }
}
그리고 마지막으로 멋진 비디오폰:

public class VideoPhone extends AbstractPhone {

    public VideoPhone(int year) {
        super(year);
    }
    @Override
    public void call(int outgoingNumber) {
        System.out.println("Connecting video call to " + outgoingNumber);
    }
    @Override
    public void ring(int incomingNumber) {
        System.out.println("Incoming video call from " + incomingNumber);
    }
  }
main() 메서드 에서 개체를 만들고 callAnotherUser() 메서드를 테스트합니다.

AbstractPhone firstPhone = new ThomasEdisonPhone(1879);
AbstractPhone phone = new Phone(1984);
AbstractPhone videoPhone=new VideoPhone(2018);
User user = new User("Jason");
user.callAnotherUser(224466, firstPhone);
// Crank the handle
// What number would you like to connect to?
user.callAnotherUser(224466, phone);
// Calling 224466
user.callAnotherUser(224466, videoPhone);
// Connecting video call to 224466
사용자 개체 에서 동일한 메서드를 호출하면 다른 결과가 생성됩니다. call 메서드 의 특정 구현은 프로그램이 실행 중일 때 전달된 특정 유형의 개체를 기반으로 callAnotherUser() 메서드 내에서 동적으로 선택됩니다 . 이것이 다형성의 주요 이점입니다. 런타임에 구현을 선택할 수 있다는 것입니다. 위에 제공된 전화 클래스의 예에서 우리는 메서드 서명을 변경하지 않고 기본 클래스에 정의된 메서드의 구현을 변경하는 트릭인 메서드 재정의를 사용했습니다. 이는 기본적으로 메서드를 대체합니다. 프로그램이 실행될 때 하위 클래스에 정의된 새 메서드가 호출됩니다. 일반적으로 메서드를 재정의할 때 @Override주석이 사용됩니다. 재정의 및 재정의 메서드의 서명을 확인하도록 컴파일러에 지시합니다. 마지막으로 Java 프로그램이 OOP의 원칙과 일치하는지 확인하려면 다음 팁을 따르십시오.
  • 물체의 주요 특성을 식별합니다.
  • 일반적인 속성 및 동작을 식별하고 클래스를 만들 때 상속을 사용합니다.
  • 추상 유형을 사용하여 개체를 설명합니다.
  • 항상 클래스의 내부 구현과 관련된 메서드 및 필드를 숨기려고 합니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION