개발/CS

상속과 조합

하프킴 2021. 5. 8. 20:31
728x90
  • 상속 :
    • 장점
      • 재사용, 중복을 줄 일 수 있음
      • 변화에 대한 유연성 및 확장성이 증가함
      • 개발 시간이 단축됨
    • 단점 :
      • 캡슐화를 깨뜨린다. (상위 클래스와 하위 클래스가 의존하게 된다)
      • 상위 클래스의 변경이 어려움
        • 하위 클래스에 영향을 주기 때문에 최악의 경우 상위 클래스의 변화가 모든 하위 클래스의 영향을 줄 수 있다.
        • 하나의 기능을 추가하기 위해 필요 이상으로 많은 수의 클래스를 추가해야 한다.(이를 클래스 폭발 문제라고 함)
      • 다중상속을 지원하지 않음.
      • protected로 선언할 경우 변수 메소드 까지 노출됨. 이로써 캡슐화가 중단됨
    • 이럴 때 사용하자
      • 확장을 고려하고 설계한 확실한 Is-a 관계일 때
        • 확실한 is-a 관계일 때, 상위 클래스는 거의 변할 일이 없다.
        • 하지만 is-a관계 일지라도 확장이 고려되지 않았다면 여전히 문제가 생길 수 있음.
      • API에 아무런 결함이 없는 경우, 결함이 있다면 하위 클래스까지 전파해도 괜찮은 경우.
  • 조합
    • 기존 클래스가 새로운 클래스의 구성 요소로 쓰인다
      • 장점
        • 메소드를 호출하는 방식으로 동작하여 캡슐화를 깨드리지 않는다.
        • 기존 클래스의 영향이 적어지며 안전하다 
        • 구현에 의존적이지 않다. (객체의 구현이 아닌 퍼블릭 인터페이스에만 의존함)
        • 컴파일 타임 관계를 런타임 관계로 변경함으로써 유동적으로 문제를 해결한다. (상속은 컴파일 타임에 결정된다.)
        • 상속보다 테스트가 쉽다. 쉽게 Mock Object를 만들 수 있음. 상속에서 파생 클래스를 테스트 하려면 수퍼 클래스에 의존적이다.
        • 다중상속에 용이함. (mixin)
        • 제어반전에 적합하고, 프록시 패턴, 데코레이터 패턴에서 종속성을 동적으로 주입할 수 있음.
  • Aggregation 집합관계
    • 집합관계 : 부분과 전체가 다른 생명주기를 가질 수 있다
      • 집 vs 냉장고
    • 구성관계 : 부분은 전체와 같은 생명주기를 갖는다
      • 사람 vs 심장
  • is-a관계
    • ~는 ~이다. 라는 관계가 성립
    • 학생은 사람이다.
  • has-a 관계
    • ~는 ~를 포함한다. 라는 관계가 성립. 학생클래스가 사람 클래스를 상속받고 있다. 
    • 다른 객체를 받아들이며 그 객체의 기능을 사용 하는 것.
    • 받아들인 객체의 메소드와 변수를 사용할 수 있다.
    • 어떤 클래스 A가 또 다른 클래스인 B가 가진 기능을 사용하고 싶을 때 B 클래스를 포함하면 B의 기능을 사용할 수 있다.

 

  • 객체지향의 5대 원칙
  • 단일 책임의 원칙 : 해당 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다. - 로버트 C 마틴
    • 모든 클래스는 하나의 책임만 가져야 한다.
    • 즉 클래스는 그 책임을 완전히 캡슐화 해야한다.
  • OCP개방폐쇄의 원칙 :
    • 소프트웨어 엔티티는 확장에 대해서 열려 있어야 하지만, 변경에 대해서는 닫혀 있어야 한다.
    • 즉 자신의 확장에는 열려있고, 주변의 변화에 대해서는 닫혀 있어야 한다.
    • JDBC를 사용하는 클라이언트는 DB를 오라클에서 MySQL로 바꾸더라도 Connection을 설정하는 부분 외에는 따로 수정할 부분이 없다.
    • 자바 애플리케이션은 데이터베이스라고 하는 변화에 영향을 받지 않는다. 주변에 변화에 대해서 닫혀 있지만, 데이터베이스를 교체한다는 것은 데이터베이스가 자신의 확장에는 열려 있다는 것이다.
    • 자바에도 이 원칙이 적용되어 있다. 자바에서 소스코드는 운영체제의 변화에 닫혀 있고. 각 운영체제별 JVM은 확장에 열려 있는 구조가 되는 것이다.
    • iBatis, MyBatis, 하이버네이트 등등 데이터베이스 프로그래밍을 지원하는 라이브러리와 프레임워크에서도 찾아볼 수 있다.
    • 즉 "자신의 확장에는 언제나 열려있고, 주변에 변화에는 닫혀 있어야 한다." 자신은 얼마든지 다른 것으로 교체되어도 되고, 다른 것이 교체되어도 영향이 있으면 안된다."
  • ICP 리스코프 치환 원칙
    • "서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다."
    • 의역하면, 하위 클래스는 상위 클래스의 역할을 하는데 문제가 없어야 한다.
    • 예를 들어 아버지 <- 아들 이런 구조는 논리에 맞지 않으나, 포유류 <- 고래는 하위 클래스가 상위 클래스의 역할을 하는데 전혀 문제가 없다.
    • 부모 클래스가 들어갈 자리에 자식 클래스를 넣어도 계획대로 잘 동작해야 한다.
    • 즉 "서브 타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다."

 

  • ISP 인터페이스 분리 원칙
    • "클라이언트는 자신이 사용하지 않는 메소드에 의존 관계를 맺으면 안된다."
    • 클라이언트의 요구에 따라 특화된 인터페이스를 만들어 분리시키는 설계원칙.
    • 인터페이스는 일종의 방화벽 역할을 수행해, 클라이언트는 자신이 사용하지 않는 메소드에 생긴 변화로 인한 영향을 받지 않게 된다.
    • 인터페이스 최소주의 원칙 : 인터페이스를 통헤 메서드를 외부에 제공할 때 최소한의 메소드만 제공하라는 원칙.
    • 즉 "클라이언트는 자신이 사용하지 않는 메소드에 의존관계를 맺으면 안된다."

 

  • DIP 의존 역전 원칙
    • "고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화 된 것에 의존해야 한다. 추상화된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상적인 것에 의존해야 한다. 자주 변경되는 구채 클래스에 의존하지 마라"
    • 자신보다 변하기 위운 것에 의존하던 것을 추상화된 인터페이스나 상위클래스를 두어 변하기 쉬운 것의 변화에 영향을 받지 않게 하는 것 
    • 상위 클래스일수록, 인터페이스일수록, 추상클래스일수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스에 의존하라는 것이다.
    • 즉 "자신보다 변하기 쉬운 것에 의존하지 마라"

 

 

  • Decorator 패턴
    • 객체의 결합을 통해 기능을 유연하게 확장 해주는 패턴
    • 프록시 패턴과 구현 방법이 같다
    • 다만 프록시 패턴은 크라이언트가 최종적으로 돌려 받는 반환값을 조작하지 않고 그대로 전달하면 반면 데코레이터 패턴은 클라이언트가 받는 반환값에 장식을 덧입힌다.
    • 규칙
      • 장식자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이때 인터페이스를 사용한다.
      • 장식자는 실제 서비스에 대한 참조 변수를 갖는다 (합성)
      • 장식자는 실제 서비스의 같은 이름을 가진 메서드를 호출하고 그 반환값에 장식을 더해 클라이언트에게 돌려준다.
      • 장식자는 실제 서비스의 메소드 호출 전후에 별도의 로직을 수행할 수도 있다.
    • 프록시 패턴과 동일한 구조를 갖기에 데코레이터 패턴도 개방폐쇄원칙과OCP와, 의존성 역전원칙DIP이 적용된 설계 패턴임.
  • Structural 패턴 (구조 패턴)
    • 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
    • 예를 들어 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴.
  • Strategy 패턴 
    • 행위를 클래스로 캡슐화 해 동적으로 행위를 자유롭게 바꿀 수 있는 패턴
    • 템플릿 메서드 패턴과 유사함
    • 템플릿 메서드 패턴은 상속을 이용하고. 전략 패턴은 DI를 통해 해결한다.
    • 개방폐쇄 원칙과, 의존역전원칙이 적용된 설계 패턴.
    • 세가지 요소
      • 전략 메서드를 가진 전략 객체
      • 전략 객체를 사용하는 컨텍스트
      • 전략 객체를 생성해 컨텍스트에 주입하는 클라이언트
public class Clinet {
	public static void main(String[] args) {
    	Strategy strategy = null;
        Soldier rambo = new Solider();
        
        strategy = new StrategyGun(); // 새로운 Starategy를 생성함.
        ranbo.runContext(strategy);
        }
    }
  • Singleton패턴
    • 인스턴스를 하나만 만들어 사용하기 위한 패턴이다. 커넥션 풀, 스레드 풀, 디바이스 설정 객체 등과 같은 인스턴스를 여러 개 만들게 되면 불필요한 자원을 사용하게 되고, 또 프로그램이 예상치 못한 결과를 낳을 수 있다. 
    • 구현 원칙 : 
      • 인스턴스를 하나만 만들고 그것을 계속해서 재사용 한다.
      • new를 실행할 수 없도록 생성자에 private 접근 제어자를 지정한다.
      • 유일한 단일 객체를 반환할 수 있는 정적 메서드가 필요하다.
      • 유일한 단일 객체를 참조할 정적 참조 변수가 필요하다. 
    • 단일 객체인 경우 결국 공유 객체로 사용되기 때문에 속성을 갖지 않게 하는 것이 정석이다. 만약 속성을 갖게 되면 하나의 참조 변수가 변경한 단일 객체의 속성이 다른 참조 변수에 영향을 미치기 때문이다.
public class Singleton{ 
	static Singleton singletonObject // 정적 참조 변수
    
    private Singleton() {}; // private한 생성자
    
    // 객체 반환 정적 매소드
    
    public static Singleton getInstance() {
    	if(singletonObject == null) {
        	singletonObject - newSingleTon =();
         }
         
         return singleTonObject;
    } 
} 

 

 

  • Template Method 패턴(상속을활용)
    • 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화 해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내역을 바꾸는 패턴
    • 즉 전체적으로는 동일하면서, 부분적으로는 다른 구문으로 구성된 메소드의 코드 중복을 최소화 할 때 유용함.
    • 동일한 기능을 상위 클래스에서 정의하면서, 확장/변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다.
    • 상위 클래스의 견본 메소드에서 하위 클래스가 오버라이딩한 메서드를 호출하는 패턴
    • 즉 상위 클래스에 공통 로직을 수행하는 템플릿 메서드와 하위 클래스에 오버라이딩을 강제하는 추상 메서드 또는 선택적으로 오버라이딩할 수 있는 훅 메서드를 두는 패턴.
    • DIP 패턴을 이용하고 있음을 알 수 있음.
    • 상속을 활용함.

// 상위 클래스에 템플릿을 제공하는 메서드와 하위 클래스에게 구현을 강제하는 추상메서드, 선택적으로 오버라이딩 할 수 있는 일반 메서드가 있음.
// 의존성 역전 원칙이 적용됨을 알 수 있음.

public abstract class Animal{

	// 템플릿 메서드
	public void playWithOwner() {
    
    	
    
    }
    
    abstract void play();
    
    
    // 훅 메소드
    void run someThing() {
    
    } 



} 
  • Template Callback 패턴
    • 전략 패턴의 변형, DI에 사용하는 특별한 형태의 전략 패턴.
    • 전략 패턴과 모든 것이 동일한데, 전략을 익명 내부 클래스로 정의해서 사용한다는것이 특징임.
    • 전략 패턴의 일종이므로 OCP, DIP이 적용된 설계 패턴임.
public class Clinet {
	public static void main(String[] args) {
    	Strategy strategy = null;
        Soldier rambo = new Solider();
        
        strategy = new StrategyGun(); // 새로운 Starategy를 생성함.
        ranbo.runContext(new Strategy(){
        
        @Override
        public void run Strategy(){
        
        }
        })
        }
    }
  • Factory Method패턴
    • 오버라이드된 메서드가 객체를 반환하는 패턴
    • 팩터리는 공장을 의미. 공장은 물건을 생산하는데 객체지향에서는 객체를 생성함.
    • 팩터리 메서드 : 객체를 생성하여 반환하는 메서드
    • 하위 클래스에서 팩터리 메서드를 오버라이딩 해서 객체를 반환하게 함.
public class Driver {

	public static void main() {
    
		Animal bold = new Dog();
        Animal kitty = new Cat();
        
        
       	// 팩터리 메서드가 반환하는 객체들.
        // 오버라이드된 메서드가 객체를 반환하는 패턴.
        // 의존 역전 원칙이 적용됨.
        
        AnimalToy boldBall = bold.getToy();
        AnimalToy kityTower = kitty.getToy();
        
    
	}


} 
  • Adapter패턴
    • 인터페이스를 통해 클래스들이 함께 작동하도록 해주는 패턴
    • 기존에 있는 시스템에 새로운 써드파티 라이브러리가 추가된다던지, 레거시 인터페이스를 새로운 인터페이스로 교체하는 경우에 코드의 재사용성을 높일 수있는 방법
    • JDBC를 통해 단일 인터페이스로 조작할 수 있고. JRE가 플랫폼에 상관없이 동작하는 것도 어댑터패턴 이라고 할 수 있다.
    • SOLID중 OCP의 원칙을 따른 것중 하나이기도 하다.

 

  • Proxy 패턴 : 
    • 프록시 : 대리자, 대변인. 누군가를 대신해 그 역할을 수행하는 존재. 
    • 실제 서비스 객체가 가진 메서드와 같은 이름의 메서드를 사용한다. 이를 위해 인터페이스를 사용한다.
    • 인터페이스를 사용하면서 서비스 객체가 들어갈 자리에 대리자 객체를 대신 투입해 클라이언트 쪽에서는 실제 서비스 객체를 통해 메서드를 호출하고 반환값을 받는지, 대리자 객체를 통해 메서드를 호출하고 반환값을 받는지 전혀 모르게 처리할 수 있다.
    • 주요원칙
      • 대리자는 실제 서비스와 같은 이름의 메서드를 구현한다. 이 때 인터페이스를 사용한다.
      • 대리자는 실제 서비스에 대한 참조 변수를 갖는다. (합성)
      • 대리자는 실제 서비스와 같은 이름을 가진 메소드를 호출하고 그 값을 클라이언트에게 돌려준다.
      • 대리자는 실제 서비스의 메소드 호출 전후에별도의 로직을 수행할 수도 있다.
    • 제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용한다.
    • 즉 "제어 흐름을 조정하기 위한 목적으로 중간에 대리자를 두는 패턴"
public interface IService {
	String runSomething();

}

// 실제 서비스
public class Service implements IService {
	public String runSomething() {
    	return "Hello World"
    }
}

// 실제 서비스의 메서드를 가로채는 프록시 클래스
// 실제 서비스 객체가 가진 메서드와 같은 이름의 메서드를 사용함.
pulic class Proxy implements IService {
	IService service1; // 합성을 통해 참조변수를 갖는다.
    // 이를 통해 개방 폐쇄의 원칙과, 의존 역전 원칙도 지켜진다.
    
    public String runSomething() {
    // 이곳에서 흐름을 제어함.
    	service1 = new Service();
        return service1.runSomething();
    }
}

public class ClientWithProxy{
	public static void main(String[] args) {
    
    IService proxy = new Proxy();
    
    }
}

 

  • DI
    • IoC 컨테이너에서 빈 객체를 생성하는 방식
    • new로 생성하는 것을 외부에서 주입하는 방식으로 Spring 에서는 Bean 생명주기를 관리하여 준다.
  • IoC
    • 필요한 핵심 객체를 다른 곳에서 관리하는 것
    • 일반적으로 자기가 사용할 의존성을 자기가 만들어서 사용한다.
    • 하지만 IoC에서는 직접 의존성을 관리하는 것이 아니라 외부에서 주입.
    • 스프링에서는 Bean들을관리하기 위해  IoC라는 개념을 이용하여, 외부에서 객체를 생성하고, 생성자를 이용하여 객체를 주입.
    • 인스턴스의 생성부터 소멸까지의 인스턴스의 생명주기 관리를 컨테이너가 대신 해준다는 의미
  • AOP
    • 관점 지향 프로그래밍
    • 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 모듈화 하겠다는 패러다임
    • Proxy 패턴을 발전시켜 만들어짐.
  • PSA
    • 환경에 변화와 관계 없이 일관된 방식의 기술로의 접근 환경을 제공하려는 추상화 구조

 

참고

gmlwjd9405.github.io/2018/07/09/decorator-pattern.html

서적 : 스프링입문을 위한 객체지향의 원리와 이해

Liskov Substitution Principle

python-patterns.guide/gang-of-four/composition-over-inheritance/