🎁[속보] 인프런 내 깜짝 선물 출현 중🎁

SOLID의 모든 원칙은 결국 "추상화"로 수렴한다.

1주일 동안의 강의 내용

결론부터 말하자면 SOLID원칙 모두 구체적인 구현에 의존하지말고 추상화를 통해 유연한 설계를 하라는 점에서 공통된다.

1⃣ OCP (개방-폐쇄 원칙: Open-Closed Principle)

“확장은 가능하지만, 기존 코드는 수정하지 말것”

새로운 기능을 추가할 때 기존 코드를 변경하지 않고, 새로운 기능을 담당하는 객체를 생성하고 새로운 기능과, 기존 기능을 확장할 수 있도록 인터페이스를 활용하여 설계를 가능하게 해야한다.

추상화 적용 → Shape 인터페이스를 만들고 Circle, Rectangle 등이 이를 구현하면 Shape을 확장해도 기존 코드 변경 없이 새로운 도형을 추가할 수 있음.

interface Shape {
    double area();
}

class Circle implements Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    public double area() { return Math.PI * radius * radius; }
}

class Rectangle implements Shape {
    private double width, height;
    public Rectangle(double width, double height) { this.width = width; this.height = height; }
    public double area() { return width * height; }
}

// 기존 코드 수정 없이 새로운 도형 추가 가능

해당 부분에서 Triangle과 같은 새로운 기능이 추가 되어도, 기존의 코드를 수정하는 것이 아닌 Shape를 구현할 수 있게 해주면 된다.

2⃣ ISP (인터페이스 분리 원칙: Interface Segregation Principle)

“인터페이스는 클라이언트가 사용하지 않는 기능을 강요해서는 안된다”

하나의 거대한 인터페이스, 즉 하나의 개념에 대해서 그 개념을 모두 통괄할 수 있는 인터페이스를 만드는 것이 아닌 역할별로 분리된 작은 인터페이스를 설계해야 한다.

interface Bird {
	void fly();
}

class ButterFly implements Bird {
	void fly() {
	 System.out.println("fly");
  }
}

class Penguin implements Bird {
	 void fly() {
		 	 System.out.println("can't fly...only walk");
	 }
}

✅ 현재 Penguin은 날지 못하지만 Bird란 추상적인에 속해 fly를 강요받는다…

추상화 적용 → 여러 개의 인터페이스로 분리하여 불필요한 의존성을 줄이고, 유연한 설계 가능.

interface Fly {
 void fly();
}
interface Walk {
	void walk();
}

이렇게 기능을 작은 단위의 인터페이스로 쪼개서 사용하면 된다.

3⃣ LSP (리스코프 치환 원칙: Liskov Substitution Principle)

“하위 클래스는 상위 클래스의 기능을 대체할 수 있어야 한다”

상위 클래스 타입으로 하위 클래스를 사용해도 정상적으로 동작해야 한다. 이를 위해 추상화된 부모 클래스를 만들고 이를 확장하는 하위 클래스 들이 부모의 행위를 그대로 유지해야 한다.

추상화 적용 → 하위 클래스는 상위 클래스의 동작을 유지해야 함.

class Bird {
    void fly() { System.out.println("Flying..."); }
}

class Sparrow extends Bird { } // 정상적으로 동작

class Ostrich extends Bird {
    @Override
    void fly() { throw new UnsupportedOperationException("I can't fly!"); } // LSP 위반
}

✅ Ostrich는 Bird이지만 날 수 없음 → fly()를 강제하는 구조 자체가 잘못됨

이를 해결하려면 Flyable 인터페이스를 분리하여, 날 수 있는 새만 이를 구현하도록 해야 함

4⃣ DIP (의존성 역전 원칙: Dependency Inversion Principle)

“상위 모듈이 하위 모듈에 의존하는 것이 아니라 둘 다 추상화(인터페이스)에 의존해야 한다.”

즉 구체적인 클래스가 아니라 인터페이스나 추상 클래스를 사용하여 의존성을 주입받아야 한다.

interface Repository {
		void save();
}
class MySQLRepository implements Repository{
		void save() {
			System.out.println("MySQL Save Processing");
		}
}
class JPARepository implements Repository {
		void save();
}

Repository라는 추상화를 적용시켜서 이를 구현한 구현체로 MySQL, JPA, MongoDB… 새로운 DB가 추가 되어도 구현체에 직접 의존하는 것이 아닌 상위 모듈을 의존성 주입받아 런타임에 구현체를 동적으로 변경할 수 있도록 구현한다.

결론: 모든 원칙이 “추상화”로 수렴한다.

📌 OCP (개방-폐쇄 원칙)추상화(인터페이스)를 이용하면 기존 코드를 수정하지 않고도 확장 가능

📌 ISP (인터페이스 분리 원칙)불필요한 의존성을 제거하고, 필요한 기능만 제공하는 인터페이스 설계

📌 LSP (리스코프 치환 원칙)추상화를 잘못하면 하위 클래스가 부모 클래스를 대체하지 못함 → 올바른 추상화가 중요

📌 DIP (의존성 역전 원칙)구체적인 구현이 아니라 추상화(인터페이스)에 의존해야 함.

📌 SRP (단일 책임 원칙)클래스는 단 하나의 책임만을 가지고 있어야 하고, 해당 클래스가 변경될 경우 그 이유는 오로지 책임에 의한 것이어야 함. → SRP 또한 각 클래스가 하나의 역할만 수행하므로 자연스럽게 추상화가 이루어지고, 다른 SOLID 원칙들고 적용하기 쉬워진다.


1주일 동안의 회고

깊은 내공이 느껴지는 강의를 듣고 과연 나의 코드는 읽기 좋은 코드는 못 될지 언정 과연 읽을만 한가?라고 생각하며 여태 했던 개인프로젝트들과 팀 프로젝트의 코드들을 살펴보았고, 이번 일주일 동안 강의에서 배운 SOLID원칙을 활용한 추상과 구체, 논리와 사고의 흐름 및 사고의 깊이 줄이기 같은 요소들을 하나둘씩 살펴보았다.

 

긴 시간은 투자하지 못했지만 OCP와 DIP, SRP 원칙만큼은 쉽게 눈에 보이는 레벨에서 고쳐나갈 수 있다고 생각하고 나름 만족할 만큼 적용했다고 생각한다.

 

나는 개인적으로 객체지향이라는 개념 자체가 너무 추상화되어있는 표현 혹은 단어라고 생각한다. 이것에 대한 개념에 대해서 알고있어도 초보자 레벨에서 이를 적용시키기란 매우 어렵고, 사람마다 그 기준이 너무나도 다르기 때문에 정답은 없는 것 같다라는 생각이 든다. 정답은 없지만 정답에 가까운 그런 방식은 있을 것이라고 생각한다.

 

나는 사실 내가 객체지향을 초보자 레벨에서는 나름 잘 적용해왔다고 생각해 왔었다. 그 이유는 우테코 프리코스를 2번이나 해보면서(2번 다 떨어졌다…) 그리고 실제로 친한 중고등학교 친구가 배민에서 개발자로 일하기 때문에 친구가 일주일에 한번씩 코드리뷰도 해주었었다.(많이 혼났었지만…) 나름 객체지향이라는 개념과 SRP, OCP, DIP라는 개념은 실무에서도 실제로 가장 강력한 무기가 될 수 있기에 잘 지켜왔었다고 생각한다.

 

결론은 “추상화”가 가장 중요한데.. 이부분이 제일 어렵다.

 

그래서 코드를 작성할 때 이 부분만큼은 옆에 메모장에 띄워놓고 익숙해질 때까지 해야한다 라고 생각한다.

1. 코드는 적절한 수준에서 추상화되어야 한다.

  • 핵심 로직을 메서드 또는 클래스로 분리하여 한 가지 역할에 집중하도록 한다.

  • 코드의 유연성과 확장성을 높이기 위해서는 인터페이스와 구현을 분리해야한다.

  • 네이밍을 통해 역할을 명확하게 전달한다

2.논리와 사고의 흐름

  • 코드는 논리적으로 자연스럽게 읽혀야 한다.

  • 코드의 동작 순서가 사람이 생각하는 방식과 유사해야 한다.

  • 조건문과 루프의 배치를 신중히 고려해야 한다.

  • 불필요한 상태 변경을 줄여야한다.

    • 여기서 사고의 깊이가 가속화 된다. 상태가 변경되는 시점을 기준으로 변경된걸 인치한 채 코드를 따라가야 한다.

3. 사고의 깊이 줄이기

  • 한번에 이해할 수 있는 코드가 가장 좋은 코드다

    • Simple is best!!!

  • 조건문을 단순화 하여라.

    • 그리고 부정문(!)을 사용할 경우 메서드명으로 그 의도를 명확히 나타내는쪽으로 코드를 작성해라

      • 이미 여기서 상태의 변경이 일어난 상태로 if문 안의 로직을 들어간다.

  • 메서드를 작게 나눠라.

    • 작게 나누지 못할 경우 추상화 혹은 다른 클래스의 책임을 나누는 것을 고려하라.

    • 분명 나는 SRP원칙을 잘 지켰다고 생각한 상태에서 좀 두꺼운 메서드를 나누려고 할 때 나누지 못하겠다면 SRP원칙을 어긴건 아닌지 다시 보자.

  • Early Return 패턴을 통해 중첩을 줄이자.

결론

적절한 추상화 → 코드가 마치 살아있고 객체지향적인 코드가 나타난다. 이는 코드의 유연성을 높인다.

논리적인 흐름 → 코드를 읽을 때 직관적인 이해를 돕기 때문에 유지보수하기 편하고, 소프트웨어의 품질을 높일 수 있다.

사고의 깊이 줄이기 → 코드를 한번에 쉽게 파악할 수 있도록 만들기 때문에 유지 보수 및 기능 추가 등등의 비용을 줄일 수 있다.


Day2, Day4 미션에 대한 회고

Day2 주제 : 추상과 구체에 대한 예시 3~5문장으로 정리하기.

[해결 과정]

  • 추상과 구체를 어떻게 정의할 것인가?

    • “추상이란 여러 개념을 일반화 하여 표현한 것이며, 구체는 해당 개념을 세부적으로 설명하는 것이다” 라는 사전적인 정의를 먼저 내가 스스로 이해하고 접근 하였다.

[접근 방식]

  • 일상에서의 추상과 구체의 예시를 찾아 정리하였다.

    • 일상 생활에서의 추상

      • 추상 → 동물

      • 구체 → 개, 고양이, 새, 쥐 … 등등

      • 의문 → 개 또한 추상적인 개념이 아닌가….?? 더 나아가서 “진돗개” , “시츄”, “셰퍼드”, “포메라니안”… 등등

  • 배운점 : 어디까지가 적절한 추상화인가….??에 대해서 깊이 고민하게 되었다…

Day4 주제 : 코드 리팩토링, SOLID에 대한 자기만의 언어로 정리.

[해결과정]

  • 사고의 깊이 줄이기

    • 이번 미션으로 내준 코드는 if ~ esle 그 내부에 또다른 if와 else..

  • 객체지향적인 사고

    • if 조건문의 로직 변경하기

[접근 방식]

  • 복잡한 if문에서 빠져나오는 것이 첫번째라 생각했고, 이는 early return으로 쉽게 해결할 수 있다.

  • getter를 통해 해당 객체의 외부 정보를 빼내와서 외부의 정보와 데이터를 가공하는 로직이 직접적으로 들어나있는 부분들을 객체 내부로 숨기고 캡슐화와 객체의 역할을 정확히 하고 나누는 방향으로 접근했다.

    • 그렇게 했을 때 책임과 역할이 객체간에 명확하게 되고, 캡슐화를 지킴으로써 유지보수성이 올라간다고 생각했다.

댓글을 작성해보세요.


채널톡 아이콘