[Spring] 객체지향 원칙(SOLID)

Shiba·2023년 7월 27일
0

🍀 스프링 정리

목록 보기
2/21
post-thumbnail

📝 Spring정리 1장에서 잠깐씩 언급되던 용어들과 그와 관련된 내용들을 정리해보도록 하자!

📘 객체지향 설계원칙(SOLID)


S 단일 책임 원칙(Single Responsiblity Principle, SRP)

"클래스를 변경하는 이유는 단 한 가지여야 한다."

🔷 정의

클래스가 변경되는 이유가 한 가지만 존재하기 위해서 하나의 책임만을 가져야 하는 것

🔸 책임이란?

어떠한 요구사항으로 코드를 수정해야할 때, 수정되어야 하는 부분

◼ 복수의 메소드를 가지면 단일 책임이 아닌 것일까?

  • 복수의 관심이 다른 메소드를 가졌더라도 여러 사용자들이 모두 관심사가 같은 메소드만을 사용한다면 단일 책임을 가진다고 볼 수 있다.
  • 복수의 메소드가 관심사가 같은 메소드들이라면 그 자체로 하나의 책임이다.

👁‍🗨 예제로 살펴보기

◼ 단일 책임 원칙을 위반한 코드

public class Dog{
	public void bark(){
    	System.out.println("짖기");
    }
    
    public void eat(){
    	System.out.println("밥먹기");
    }
}

//한 사용자는 bark()를, 다른 사용자는 eat()를 사용한다면 클래스의 변경이유가 2가지가 된다.

◼ 단일 책임 원칙을 적용한 코드

public class Dog{
	public void bark(){
    	System.out.println("짖기");
    }
}

public class Eat{
	public void eat(Dog dog){
    	System.out.println("밥먹기");
    }
}

// bark()와 eat()기능을 클래스 두개로 분리시켰다.
//eat()을 바꾸더라도 이제 Dog클래스에 변경점이 없다.

O 개방 폐쇄 원칙(Open/Closed Principle, OCP)

"확장에는 열려있고, 변경에는 닫혀있어야 한다."

🔷 정의

기존의 코드를 변경하지 않으면서 새로운 기능을 추가할 수 있도록 하는 것

👁‍🗨 예제로 살펴보기

◼ 개방 폐쇄 원칙을 위반한 코드

public class Pen{
	String color;
    public void draw(){
    	if(color = "빨강")
    		System.out.println("빨강색펜으로 그리기");
        else if(color = "검정")
    		System.out.println("검정색펜으로 그리기");
    }
}

//색을 추가하거나 삭제할때마다 if else if문에 교체가 필요

◼ 개방 폐쇄 원칙을 적용한 코드

public class Pen{
	String color;
    public void draw(){
    	System.out.println(color + "색펜으로 그리기");
    }
}

//기존의 색이 삭제되거나 새로운 색이 추가되더라도 Pen클래스에 변화가 없음

L 리스코프 치환 원칙(Liskov Substitution Principle, LSP)

"상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다."

🔷 정의

부모를 자식으로 완벽하게 대체가 가능하도록 하는 것
- 부모가 추구하는 방향과 자식이 추구하는 방향이 같아야 함

👁‍🗨 예제로 살펴보기

◼ 리스코프 치환 원칙을 위반한 코드

//가장 유명한 예제 직사각형/정사각형//

//직사각형 정의//
public class Rect{
	int w;
    int h;
    
    void setW(int w){
    	this.w = w;
    }
    
    void setH(int h){
    	this.h = h;
    }
    
    int area(){
    	return w * h;
    }
}

//정사각형 정의//
public class Square extends Rect{
	int w;
    int h;
    
    @Override
    void setW(int w){
    	this.w = w;
        setH(w); // 길이를 같게하기 위함.
    }
    
    @Override
    void setH(int h){
    	setW(h); // 길이를 같게하기 위함.
    	this.h = h;
    }
}

public class Test{
	public static void main(String[] args){
    	Rectangle r1 = new Rectangle();
        Rectangle r2 = new Square();
        
        r1.setW(3);//직사각형의 밑변
        r1.setH(10);//직사각형의 높이
        //예상 넓이 : 30
        // 3*10을 실행할 것으로 기대
        
        r2.setW(5);//정사각형의 밑변
        r2.setH(3);//정사각형의 높이
        //예상 넓이 : 15 
        // 직사각형의 로직과 같이 5*3을 실행할 것으로 기대
        
        System.out.println(r1.area()+ "  " + r2.area());
    }
}
//출력//
30  9

//결과값 자체는 직사각형/정사각형에 맞게 나오지만,
//직사각형을 만드는 동작(밑변, 높이를 받아서 두개를 곱하기) 
//정사각형을 만드는 동작(밑변, 높이중 늦게 받은 것을 기준으로 제곱하기)
// => 부모가 추구하는 방향과 자식이 추구하는 방향이 다르다! 
자식이 완벽하게 부모를 대체하지 못함! - 리스코프 치환 원칙 위배

◼ 리스코프 치환 원칙을 적용한 코드

//사각형 정의//
public class Shape{
	int w;
    int h;
    
    void setW(int w){
    	this.w = w;
    }
    
    void setH(int h){
    	this.h = h;
    }
    
    int area(){
    	return w * h;
    }
}

//직사각형 정의//
public class Rect extends Shape{
    Rect(int w, int h){
    	setW(w);
        setH(h);
    }
}

//정사각형 정의//
public class Square extends Shape{
	Square(int len){
    	setW(len);
        setH(len);
    }
    
}

public class Test{
	public static void main(String[] args){
    	Shape r1 = new Rectangle(3, 10);
        Shape r2 = new Square(3);//정사각형의 변의 길이
 
        System.out.println(r1.area()+ "  " + r2.area());
    }
}
//출력//
30  9

//부모가 추구하는 방향(밑변과 높이를 입력받아 넓이 구하기)
//자식이 추구하는 방향(밑변과 높이를 입력받아 넓이 구하기)
// 서로 같으므로 리스코프 치환 원칙을 준수

⁕ Shape클래스를 굳이 만들지 않고 Rect와 Square클래스를 생성자로 만들면 되지 않을까 잠시 생각했지만 부모의 생성자가 자식 생성자에 자동으로 삽입되어 나오기때문에 에러가 발생한다.
그렇다고 Square만 생성자로 만들게되면 부모와 자식이 입력을 받는 방법에서부터 차이가 생기게된다.


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

"클라이언트는 사용하지 않는 인터페이스에 강제로 의존해서는 안된다"

🔷 정의

자신이 사용하지 않는 메소드에 의존해서는 안된다는 원칙
- 꼭 필요한 메소드만 상속받도록 인터페이스를 작게 만들어라

👁‍🗨 예제로 살펴보기

◼ 인터페이스 분리 원칙을 위반한 코드

//탈 것 인터페이스 정의//
public interface Vehicle{
	void go();
    void fly();
    void steer();
}

//자동차 클래스//
public class Car implements Vehicle{
	void go(){
    	System.out.println("차가 달립니다.");
    }
    
    void fly(){ //불필요
    	return;
    }
    
    void steer(){ //불필요
    	return;
    }
}

//비행기 클래스//
public class Airplane implements Vehicle{
	void go(){
    	System.out.println("비행기가 육지를 달립니다.");
    }
    
    void fly(){
    	System.out.println("비행기가 납니다.");
    }
    
    void steer(){ //불필요
    	return;
    }
}

//배 클래스//
public class Boat implements Vehicle{
	void go(){ //불필요
    	return;
    }
    
    void fly(){ //불필요
    	return;
    }
    
    void steer(){
    	System.out.println("배가 바다를 달립니다.");
    }
}

//불필요한 메소드까지 상속을 받는 형태

◼ 인터페이스 분리 원칙을 적용한 코드

//탈 것 인터페이스를 작게 분리//
public interface Ground{
	void go();
}

public interface Sky{
	void fly();
}

public interface Sea{
	void steer();
}

//자동차 클래스//
public class Car implements Ground{
	void go(){
    	System.out.println("차가 달립니다.");
    }
}

//비행기 클래스//
public class Airplane implements Ground,Sky{
	void go(){
    	System.out.println("비행기가 육지를 달립니다.");
    }
    
    void fly(){
    	System.out.println("비행기가 납니다.");
    }
}

//배 클래스//
public class Boat implements Sea{
    void steer(){
    	System.out.println("배가 바다를 달립니다.");
    }
}

//필요한 메소드만 상속 받는 형태

D 의존관계 역전 원칙(Dependency Inversion Principle, DIP)

"1. 상위 모듈은 하위 모듈에 의존해서는 안되고 둘 다 추상화에 의존해야 한다."
"2. 추상화는 세부사항에 의존해서는 안되고 세부사항은 추상화에 의존해야 한다."

🔷 정의

기능을 사용하는 객체가 기능객체 그 자체에 의존해서는 안된다
- 서로 추상화된 객체를 이용하여 상호작용해야한다.

👁‍🗨 예제로 살펴보기

◼ 의존관계 역전 원칙을 위반한 코드

public class Arrow{
	String s = "활";
}

public class Sword{
	String s = "검";
}

public class Gun{
	String s = "총";
}

public class Fighter{
	Arrow arrow;
    Sword sword;
    Gun gun;
    
    void attackArrow(){
    	System.out.println(arrow.s + " 공격");
    }
    
    void attackSword(){
    	System.out.println(sword.s + " 공격");
    }
    
    void attackGun(){
    	System.out.println(gun.s + " 공격");
    }
}

//새로운 무기를 추가할 때마다 새로운 메소드와 변수가 Fighter클래스에 추가

◼ 의존관계 역전 원칙을 적용한 코드

//추상화를 위한 인터페이스 정의//
public interface Weapon{
	String s;
}

public class Arrow implements Weapon{
	String s = "활";
}

public class Sword implements Weapon{
	String s = "검";
}

public class Gun implements Weapon{
	String s = "총";
}

public class Fighter{
	Weapon weapon;
   
    void attack(){
    	System.out.println(weapon.s + " 공격");
    }
}

//새로운 무기를 추가해도 Fighter클래스엔 변화가 없음//

📚 참고자료

단일 책임 원칙 : https://jaeseongdev.github.io/development/2021/02/14/단일_책임_원칙_SRP/

개방 폐쇄 원칙 : https://steady-coding.tistory.com/378

리스코프 치환 원칙 : https://blog.itcode.dev/posts/2021/08/15/liskov-subsitution-principle

인터페이스 분리 원칙 : https://yoongrammer.tistory.com/99

의존관계 역전 원칙 : https://yoongrammer.tistory.com/100

profile
모르는 것 정리하기

0개의 댓글