SOLID (객체 지향 설계)

포모·2020년 11월 27일
0

📢 SOLID

우테코 프리코스의 미션을 하면서 어떻게 하면 더 쉬운 코드를 짤 수 있을까 고민하던 중, 올바른 객체 지향 설계(OOP, Object Oriented Programming)에 대한 필요성을 느끼고 공부한 것을 정리해보고자 합니다.

이걸 먼저 공부하고 코드를 짰어야 되는 것 아니었나 싶긴 합니다... 😂
지금이라도 해봅시다! 💪


시간이 지나도 프로그램의 유지 보수와 확장이 쉬운 시스템으로 설계할 수 있도록 하기 위해 지켜야할 원칙이 있습니다.
SOLID란 객체 지향 프로그램 설계의 다섯가지 기본 원칙을 의미합니다.
이 다섯가지 기본 원칙에 대해 알아보겠습니다.


📌 1. SRP

단일 책임 원칙 (Single responsibility principle)
모든 객체(클래스)는 하나의 책임을 가지며, 객체가 제공하는 서비스는 하나의 책임을 수행하는데 집중되어야 한다.


객체지향적으로 설계할 때 응집도를 높게, 결합도를 낮게 설계하는 것이 좋습니다.

  • 응집도 : 한 프로그램의 요소가 얼마나 뭉쳐있는가
  • 결합도 : 프로그램 구성 요소들 사이가 얼마나 의존적인가

  • 예시

    계산기를 나타내는 Calculator 객체가 있다고 가정했을 때,
    Add(), Sub(), Mul(), Div() 이외의 함수가 있다고 해봅시다.
    예를 들어 Alarm() 함수를 Calculator에 추가한다면 SRP에 위배됩니다.


여러 객체들이 하나의 책임만 갖도록 잘 분배한다면, 시스템에 변화가 생기더라도 그 영향을 최소화할 수 있기에 SRP 원칙을 잘 따를 수 있도록 설계해야합니다.


📌 2. OCP

개방-폐쇄 원칙 (Open-Closed Principle)
기존 코드를 변경하지 않으면서 (closed), 기능을 추가할 수 있도록 (open) 설계되어야 한다.


확장에는 개방적이고 수정에 대해서는 폐쇄적이어야 한다는 의미입니다.
이를 위해선 캡슐화 를 통해 여러 객체에서 사용하는 같은 기능을 인터페이스에 정의하는 방법이 있습니다.


  • Animal Interface를 구현한 각 클래스들은 crying() 함수를 재정의합니다.
public class Client {
	public static void main(String args[]) {
  		Animal cat = new Cat();
  		Animal dog = new Dog();
  
  		cat.crying();
  		dog.crying();
  }
}

📌 3. LSP

리스코프 치환 원칙 (Liskov Substitution Principle)
자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있도록 설계되어야 한다.


자식 클래스는 언제나 부모 클래스의 역할을 대체할 수 있어야 한다는 것을 의미하며, 부모 클래스와 자식 클래스의 행위가 일관됨을 의미한다고 합니다.
  • 예시
  
public abstract class Person {
  	abstract public void Work();
}

public class Programmer extends Person {
 	 public void Work() {
  		System.out.println("개발하다");
  	 }
}
  
public class Teacher extends Person {
 	 public void Work() {
  		System.out.println("가르치다.");
  	 }
}

'사람의 역할을 수행한다'는 의미에서 Programmer의 Work()와 Teacher의 Work()가 문맥상 흐름에 어색하지 않습니다.


public class Main {
  	public static void main(String args[]) {
  		Person person1 = new Programmer();
  		person1.Work();
  
  		Person person2 = new Teacher();
  		person2.Work();		
  	}
}

📌 4. ISP

인터페이스 분리 법칙 (Interface Segregation Principle)
자신이 사용하지 않는 인터페이스는 구현하지 말아야한다.


하나의 거대한 인터페이스 보다는 여러 개의 구체적인 인터페이스가 낫다는 것을 의미한다고 합니다.
SRP는 객체의 단일 책임을 뜻한다면 ISP는 인터페이스의 단일 책임을 의미합니다.


- 예시

핸드폰(Phone)은 전화(call), 문자(sms), 알람(alarm), 계산기(calculator) 등의 기능이 있습니다.
3G와 같은 옛날 폰(oldphone), 스마트폰(smartphone)은 핸드폰(Phone) 인터페이스의 모든 기능을 사용합니다.


ISP를 만족하기 위해서는 Phone 인터페이스에 call, sms, alarm, calculator를 모두 정의하기보다는 각각 정의하여 옛날폰과 스마트폰에서 4개의 인터페이스를 구현하도록 설계해야합니다.


이렇게 설계하면 각 인터페이스의 메소드들이 서로 영향을 미치지 않게 됩니다.
즉, 자신이 사용하지 않는 메서드에 대한 영향력이 줄어들게 됩니다.


📌 5. DIP

의존 역전 원칙 (Dependency Inversion Principle)
객체들이 서로 정보를 주고 받을 때 이 때 객체들은 나름대로의 원칙을 갖고 정보를 주고 받아야한다.


나름대로의 원칙이란, 추상성이 낮은 클래스보다 추상성이 높은 클래스와 의존 관계를 맺어야한다는 것을 의미합니다.
일반적으로 인터페이스를 활용하면 이 원칙을 준수할 수 있게 됩니다. (캡슐화)


Client라는 객체가 있다고 가정해봅시다.
Client 객체는 Cat, Dog, Birdcrying()에 직접 접근하지 않고 Animal 인터페이스의 crying()메서드를 호출함으로써 DIP를 만족할 수 있습니다.


  • 예시
interface Animal{
    void crying();
}

class Client {
    Animal animal;

    void setAnimal(Animal animal){
        this.animal = animal;
    }

    void crying(){
        animal.crying();
    }
}

class Cat implements Animal {
    @Override
    public void crying() {
        System.out.println("야옹");
    }
}

class Dog implements Animal {
    @Override
    public void crying() {
        System.out.println("멍멍");
    }
}

class Bird implements Animal {
    @Override
    public void crying() {
        System.out.println("짹짹");
    }
}

class Main {
    public static void main(String[] args) {
        Client client = new Client();

        client.setAnimal(new Cat());
        client.crying();    // 야옹
        
        client.setAnimal(new Dog());
        client.crying();    // 멍멍
        
        client.setAnimal(new Bird());
        client.crying();    // 짹짹
    }
}

🛴 마무리

분명 학교를 다니면서 다 배웠던 내용임에는 틀림이 없음에도 새롭게 배우는 느낌이 드네요..
왜 이렇게 짜야하는 지에 대해서 이해하고자 내용을 공부해야하다보니 자연스럽게 배울 것을 찾게 되는 것 같습니다.
정말 열심히 해야겠다는 생각이 드는 하루네요 ✍


참고

0개의 댓글