OOP

박윤택·2022년 5월 11일
3

JAVA

목록 보기
1/14
post-thumbnail

객체지향 프로그래밍(Obect-Oriented Programming)

 

🤔 정의

객체지향 프로그래밍이라는 단어를 많이 들었을것이다. 이는 프로그래밍에서 필요한 데이터들을 추상화시켜 상태와 행위를 만들고 그 객체들 간의 유기적인 상호작용을 통해 로직을 구성하는 프로그래밍 방법이다. 즉, 여러 개의 객체들로 나누고 각각의 객체들을 설계하고 그에 따른 기능들을 구현하는 방법이다.


🙄 특징

  • 추상화 : 사물들의 공통된 특징을 파악하여 이를 하나의 개념으로 다룸
  • 상속 : 새로운 클래스가 기존 클래스의 자료와 연산을 이용할 수 있게 하는 기능, 코드의 재사용 가능
  • 다형성 : 한 요소에 대해 여러 개념을 넣어 놓는 것
    - 오버라이딩 : 상위 클래스의 메서드를 하위 클래스가 재정의해서 사용
    - 오버로딩 : 같은 이름의 메서드를 매개변수의 유형과 리턴 타입을 다르게 하여 사용
  • 캡슐화 : 외부에 노출할 필요가 없는 정보 숨김

✨ 장, 단점

장점단점
재사용 가능개발 속도 느림
자연적인 모델링실행 속도가 느림
유지보수 용이코딩 난이도 높음
생산성 향상

📖 5가지 설계 원칙, SOLID "로버트 마틴"

1. SRP(Single Responsibility Principle), 단일 책임의 원칙

"한 클래스는 하나의 책임만 가져야 한다."

public class Man {
    public void work(){} // 직장에서 업무
    public void play(){} // 친구와 놀기
    public void help(){} // 봉사활동 가기
}
[SRP 위반 사항]

Man Class는 남자의 역할을 나타낸다. 직장에서 업무도 보고 친구와 놀기도 하고 봉사활동을 가기도 하는데 하나의 객체가 여러 개의 책임을 지므로 SRP 위반이 되고 있다.

public class Worker {
    public void work(){} // 직장에서 업무
}

public class Friend {
	public void play(){}
}

public class Volunteer{
	public void help(){}
}
[SRP 위반 사항 수정]

각각의 책임을 맡도록 Man Class를 여러 개의 역할로 나누어 한 클래스는 하나의 책임만 가지도록 설계를 해야한다.

2. OCP(Open-Closed Principle), 개방 폐쇄 원칙

"소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다."

public class Person {
    private Tesla car = new Tesla(); // 변경 전
    
    
    // private HyundaiCar car = new HyundaiCar(); // 변경 후
}
[OCP 위반 사항]

위의 코드에서 처음에 테슬라 차를 가지고 있다가 현대차로 자동차를 바꿀 때 코드의 변경이 발생하는데 이럴 때 클라이언트 코드(객체, 클래스를 호출하는 코드)를 수정해야 한다. 즉 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계가 되어야 하는데 그렇지 못하고 있다.

// Car interface
public interface Car {
    public void accelerate();
    public void stop();
}

// Tesla차는 Car를 implements 함
public class Tesla implements Car{
    @Override
    public void accelerate() {
        System.out.println("테슬라 가속");
    }

    @Override
    public void stop() {
        System.out.println("테슬라 정지");
    }
}

// 현대차는 Car를 implements 함
public class HyundaiCar implements Car {
    @Override
    public void accelerate() {
        System.out.println("현대차 가속");
    }

    @Override
    public void stop() {
        System.out.println("현대차 정지");
    }
}

public class Person {
    private Car car = new Tesla(); // 변경 전
    
    
    //private Car car = new HyundaiCar(); // 변경 후
}
[OCP 위반사항 수정]

Car interface를 생성하고 Tesla차와 현대차를 implements하였다. 만약에 새로운 차로 바꾸고 싶다면 Car를 implements 받는 새로운 class를 선언하고 MyCar 클래스에서 인스턴스만 새로 만든 클래스로 바꿔주면 OCP 원칙을 지키게 된다.

3. LSP(Liskov Substitution Priciple), 리스코프 치환 원칙

"프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다."
즉, 클래스 A가 클래스 B를 상속받았다면 프로그램의 동작을 방해하지 않고 B를 A로 바꿀 수 있어야한다.

// 직사각형
public class Rectangle {
	private int width;
    private int hegiht;
    
    public int getArea(int width, int height) {
    	return widht * height;
    }
    // getter, setter, constructor
}

// 정사각형
public class Square extends Rectangle {
	private int width;
    private int height;
    
	@Override
    public void setWidth(int width){
    	this.width = width;
        this.height = width;
    }
    
    @Override
    public void setHeight(int height){
    	this.width = height;
        this.height = height;
    }
}

public class Main {
	public static void main(String[] args){
    	Rectangle rec = new Square();
        rec.setWidth(6);
        rec.setWidth(5);
        Sytsem.out.println(rec.getArea()); // 30 출력
    }
}
[LSP 위반사항]

Square class는 Rectangle class를 상속받고 있다. LSP 원칙에 따르면 프로그램의 정확성을 깨면서 하위 타입의 인스턴스로 바꾸고 있다. 자식 클래스가 부모 클래스의 역할을 수행해야 하는데 넓이를 구하는 함수 getArea() 함수에서 오류가 발생하였다. 정사각형 클래스의 경우 높이, 너비 값이 같도록 설계를 하였지만 자식 클래스를 받는 직사각형 클래스는 넓이를 가로 * 세로로 계산하므로 오류가 발생하고 있다.

해결 방법으로는 상속 관계를 없애는 방법과 getArea() 메서드를 오버라이딩하여 LSP 원칙을 유지할 수 있다.

4. ISP(Interface Segregation Principle), 인터페이스 분리 원칙

"특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다."

앞서 확인한 SRP와 비슷한 개념이다.

public interface Man() {
	public void work(){} // 직장에서 업무
    public void play(){} // 친구와 놀기
    public void help(){} // 봉사활동 가기
}

public class Me implements Man {
	@Override
    public void work() {}
    
    @Override
    public void play() {}
    
    @Override
    public void help() {}
}
[ISP 위반 사항]

나를 나타내는 class를 사용하기 위해서는 interface로 선언한 Man에 구현되어 있는 함수들을 모두 오버라이딩 작업을 해야하며 만약 현재 내가 직장에서 업무를 보고 있다면 친구와 놀기, 봉사활동 가기는 따로 구현할 필요가 없어지게 된다.

public interface Worker() {
	public void work(){} // 직장에서 업무
}
public interface Friend() {
	public void play(){} // 친구와 놀기
}
public interface Volunteer() {
	public void help(){} // 봉사활동 가기
}

public class Me implements Worker {
	@Override
    public void work() {}
}
[ISP 위반 사항 수정]

현재 내가 일을 있다면 Worker interface를 implemnts 받아 work() 메서드를 오버라이딩 해서 사용하면 될 것이고, 어제의 나를 객체로 표현한다고 가정했을 때 일도 하고 친구와 놀기도 했다면 implments를 여러개 할 수 있으므로 Friend interface를 받아와서 표현할 수도 있다.

5. DIP(Dependency Inversion Principle), 의존 역전 원칙

"추상화에 의존해야지, 구체화에 의존하면 안된다."

public class Person {
    private Tesla car = new Tesla(); 
}
[DIP 원칙 위반 사항]

앞서 언급한 OCP에서 사용한 예를 들어 설명하면 운전자는 Tesla 클래스를 운전자의 자동차로 선언을 하였다. 만약에 운전자의 자동차를 바꾸는 상황이 온다면 직접 Person 클래스에서 변경을 해야하기도 하고 운전자가 자동차를 이용해서 어떤 행위를 진행하는 것을 메서드로 정의를 하였다면 그 메서드 또한 변경을 해야한다.

public interface Car {
	public void accelerate();
    public void stop();
}

public class Tesla implements Car{
	@Override
    public void accelerate() {
        System.out.println("테슬라 가속");
    }

    @Override
    public void stop() {
        System.out.println("테슬라 정지");
    }
}

public class Hyundai implements Car{
	@Override
    public void accelerate() {
        System.out.println("현대차 가속");
    }

    @Override
    public void stop() {
        System.out.println("현대차 정지");
    }
}

public class Person {
	private Car car;
    
    public void setCar(Car car){
    	this.car = car;
    }
    public Car getCar(Car car){
    	return this.car;
    }
}

public class Main {
	public static void main(String[] args){
    	Person person = new Person();
        person.setCar(new Tesla());
        // person.setCar(new Hyundai()); // 차 변경
        
        person.getCar().accelate();
    }
}
[DIP 원칙 위반사항 수정]

운전자가 차를 바꾸게 된다면 Person 클래스의 setCar() 메서드를 이용하여 간편하게 자동차를 변경할 수 있게 된다. 자동차의 추상화 과정을 거쳤기 때문에 자동차 종류를 바꾼다하더라도 운전자 class가 영향을 받지 않게 된다.

0개의 댓글