OOP의 특징과 SOLID에 대하여

wannabeking·2022년 7월 7일
1

CS

목록 보기
8/27
post-thumbnail

PP(Procedure Programming)란?

PP: 절차적 프로그래밍

  • 과거에는 큰 규모의 하드웨어와 소프트웨어가 필요하지 않았기 때문에 PP를 사용하였다.
  • 말로 설명하자면, 기능을 중심으로 '무엇을 어떤 절차로 할 것인가?'와 같다.
  • PP는 말그대로 순차적으로 처리하는 컴퓨터의 처리구조와 비슷하여 속도가 빠르다는 장점이 있다.
  • 하지만 시대가 지날수록, 코드가 더욱 복잡해질수록 PP의 단점인 유지보수가 힘들고 이른바 '스파게티 코드'가 될 수 있다는 점이 부각된다.


OOP(Object Oriented Programming)란?

OOP : 객체지향 프로그래밍

  • OOP는 객체가 중심이 된다.
  • 즉, '어떠한 객체가 어떤 상태를 가지고 어떤 행위를 하는가?'와 같다.
  • 예를들어 Dog라는 클래스가 존재하고, name 필드와 bark()라는 메소드를 가지는 것이다.
  • OOP하면 4가지 특징들이 바로 떠올라야 하며 그것들은 다음과 같다.
    • 캡슐화
      • 이번엔 예를들어 Account라는 클래스가 있다고 가정하자.
      • Account에는 name, number, amount 등이 있을 것이다.
      • 또한 메소드로는 withdraw(), save() 등이 있을 것이다.
      • 우리는 계좌에 관련된 필드, 메소드를 묶어 Account라는 클래스를 만들었고 이 것이 캡슐화이다.
      • 만약 계좌의 amount를 아무나 접근 가능하다면? 문제가 발생할 것이다. amount 값만 바꿔주면 되기 때문이다.
      • 하지만 amount를 private로 만들어주고 getter, setter나 다른 메소드 등을 통해서 접근하게 한다면 amount를 실수로 수정하는 일이 없을 것이다. 이 또한 캡슐화이다.
      • 그렇다면, 다른 클래스에서 Accountsave()를 호출한다고 save()의 내용을 알 수 있는가? 그것 또한 아니다. 이 것또한 캡슐화이다.
      • 즉, 관련된 것들을 하나의 클래스로 만들면서 정보 은닉이라는 특징을 가지게 된 것이다.
    • 추상화
      • 공통의 속성이나 기능들을 추출하여 묶는 것이다.
      • 우리는 웹앱 등을 개발하면서 다양한 저장소를 만들 수 있을 것이다.
      • 저장소는 로컬 저장소가 될 수도 있고, MySQL 등 RDBMS를 연결할 수도 있을 것이고, csv 파일이 될 수도 있을 것이다.
      • 하지만 결국 목적은 동일할 것이다. save() 또는 find() 등을 통하여 데이터를 저장하고, 읽고, 업데이트하고, 삭제할 것이기 때문이다.
      • 그렇다면 우리는 로컬, MySQL, csv 파일을 사용하여 데이터를 관리하는 공통적인 기능들을 묶을 수 있을 것이다.
      • Java로 예를들면 Repository라는 인터페이스를 만들고, LocalRepository, MySQLRepository, CSVRepository와 같이 구현할 것이다.
      • 그러면 Repository에 메소드를 정의하고 나머지 구현체들에서 오버라이딩할 것이다. 이 것이 추상화이다.
      • 결국 우리는 저장소가 가지는 공통의 속성이나 기능들을 묶어서 Repository라는 인터페이스를 만들었기 때문이다.
    • 다형성
      • 다형성이란 프로그램 언어의 각 요소들이 다양한 자료형에 속하는 것이 허락되는 것이다.
      • 또는 여러 형태를 가지고 있어 상황에 따라 의미를 다르게 부여할 수 있는 것도 다형성이다.
      • 대표적으로 Java에서 오버로딩과 오버라이딩이 있을 것이다.
      • 오버로딩은 add()라는 함수가 있다고 가정하자.
      • add(int num1, int num2), add(int num1, int num2, int num3)가 존재하고 두 메소드 모두 인자로 전달된 수들을 더해서 반환한다.
      • 그렇다면 같은 add()인데 인자의 개수에 따라 두 수의 합, 세 수의 합으로 바뀌게 된다. 이 것이 다형성이다.
      • 오버라이딩도 간단히 설명하면 하나의 인터페이스의 여러 구현체가 같은 메소드를 다르게 오버라이딩 하면 같은 메소드인데 기능이 다 다를 것이다.
      • 상속 받아서 오버라이딩하는 경우도 마찬가지로 부모의 메소드와 자식의 메소드의 기능이 다를 것이다. 이 또한 다형성이다.
      • 마지막으로, ArrayListList를 구현한 AbstractList를 상속한다. LinkedList도 마찬가지이다.
      • 따라서 같은 List여도 다른 형태를 가질 수 있고 이 또한 다형성이다.
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = new LinkedList<>();
    • 상속
      • 상속은 재사용성과도 같다.
      • 상위 클래스에 근거하여 새로운 클래스를 만들 수 있기 때문이다.
      • 만약 상속이 없다면 업데이트할 것이 생기면 항상 기존의 클래스를 수정해야 할 것이다. 하지만 이 방법은 다음과 같은 애로사항이 존재한다.
      • TV 클래스가 존재한다. 이 TV는 현재 QHD 화질이다.
      • UHD가 대세이다. 따라서 TV를 UHD로 업데이트 했다.
      • 근데 QHD가 필요하다...? 이미 UHD로 업데이트 했는데?
      • 이럴 때 상속을 해결하면 간단하다.
      • TV를 상속받아 QHDTV를 만들고, 또 TV를 상속받아 UHDTV를 만든다. 그렇다면 QHD, UHD 모두 사용 가능하다.


SOLID란?

  • 앞서 설명한 4가지가 OOP의 특징 이었다면, 이번엔 OOP의 원칙이다.

  • 5대 원리들의 앞글자를 따와 SOLID라 부른다.

  • SRP(Single Reponsibility Principle)

    • 단일 책임 원칙이다.
    • 클래스는 단 하나의 책임을 가져야 된다는 것이다.
    • 책임이 많아지면 응집도가 높아지고 유지보수가 힘들어 진다.
    • 예를 들어 Naver라는 클래스가 존재한다고 가정하자.
    • Naver는 가지고 있는 책임이 너무나도 많다.
    • 예를 들어 네이버 웹툰이 있을 것이고... 네이버 블로그도 존재할 것이며... 날씨... 쇼핑 라이브... 수도 없이 존재한다.
    • 이 중 네이버 웹툰을 수정해야 한다면 엄청 힘들 것이다.
    • 그래서 나누는 것이다. NaverWebtoon, NaverBlog 등으로.
  • OCP(Open Close Principle)

    • 계방 폐쇄 원칙이다.
    • 확장에는 열려있고, 변경에는 닫혀있어야 한다는 원칙이다.
    • 아까 예를 들었던 Repository를 다시 가져와 보자.
    • 만약 Repository로 추상화를 하지 않았다고 가정해보자.
    • 처음에는 로컬에만 저장하는 중이라 LocalRepository만 존재 했다.
    • 그러던 중 메모리, 휘발성 문제로 RDBMS를 도입했다.
    • 그러면 우리는 LocalRepositoryMySQLRepository로 변경할 것이다.
    • 이러면 OCP를 어기는 것이다. 우리는 Repository라는 인터페이스를 가지고 다양한 형태의 저장소를 확장시켜야 하는 것이다.
  • LSV(The Liskov Substitution Principle)

    • 리스코브 치환 원칙이다.
    • 서브타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 원칙이다.
    • 이 것도 아까 예를 들었던 ArrayListLinkedList를 가져와 보자.
    • add(ArrayList list)라는 메소드가 존재한다 이 메소드는 list에 무언가를 추가한다. 그렇다면 이 메소드의 인자로 LinkedList를 사용할 수 있는가?
    • 그렇다면 다시, add(List list)라는 메소드가 존재한다. 이 메소드는 둘 다 사용 가능한가?
    • 결국 우리는 기반 타입으로 교체하여 더 폭넓은 메소드를 만들 수 있었다. 이 것이 LSV이다.
  • ISP(Interface Segregation Principle)

    • 인터페이스 분리 원칙이다.
    • SRP랑 비슷한데, 이번엔 인터페이스 버전이다.
    • 인터페이스의 책임을 분리하라는 원칙이다.
    • 예를 들어 PhoneService 라는 인터페이스가 있다고 가정하자. 예시를 몇 번째 드는지 모르겠다.
    • 예전 핸드폰은 지금처럼 문자, 전화, 인터넷, 카메라 등이 가능했다. 하지만 요즘 스마트폰은 어플, 음성인식 등이 가능하고 반면에 예전 핸드폰에만 DMB, 라디오 등의 기능이 존재했다. 물론 어플 받으면 지금도 되지만...
    • 그렇다면 PhoneService에는 이 기능들이 모두 포함될 것이다. 현재로 DMB, 라디오 기능이 들어간 스마트폰도 존재하기 때문이다.
    • 이제 하나의 인터페이스의 너무 많은 책임이 존재하는 것이다. 따라서 DMBService, RadiService, MessageService 등과 같은 인터페이스로 불리시켜 하나의 책임을 가지게 만드는 것이 ISP 이다.
  • DIP(Dependency Inversion Principle)

    • 의존성 역전의 원칙이다.
    • 하위 레벨 모듈의 변경이 상위 레벨 묘듈의 변경을 요구하는 위계 관계를 끊는 것이다.
    • 즉, 상위 모듈이 하위 모듈에 의존하면 안되는 것이다.
    • 예를 들어 소셜 로그인 기능을 추가하기로 하였다고 가정하자.
    • 처음에 카카오 로그인을 선택하였고, 해당 기능에만 집중했다.
    public class KakaoLogin {
    	
        String login() {
        	return "Kakao";
        }
    }
    public class SocialLoginService {
    
    	private KakaoLogin login;
        
        public void setLogin(KakaoLogin login) {
        	this.login = login;
        }
        
        public void login() {
        	return login.login();
        }
    }
    • 이 경우에 카카오 로그인에서 네이버 로그인으로 변경한다고 가정하자.
    • 그렇다면 상위 모듈인 SocialLoginService또한 변경해야 할 것이다.
    • 그렇다면 DIP를 위반하는 것이 된다. 그렇다면 어떻게 리팩토링할 수 있을까?
    public class KakaoLogin implements Login{
    
    	String login() {
        	return "Kakao";
        }
    }
    public class NaverLogin implements Login{
    
    	String login() {
        	return "Naver";
        }
    }
    public class SocialLoginService {
    
    	private Login login;
        
        public void setLogin(Login login) {
        	this.login = login;
        }
        public void login() {
        	return login.login();
        }
    }
    • 이제 우리는 하위 모듈의 변경에도 상위 모듈을 변경하지 않아도 된다.
    • SocialLoginServicelogin 필드만 setter를 사용하여 원하는 소셜 로그인을 넣어주면 된다.


profile
내일은 개발왕 😎

0개의 댓글