객체지향
: 문제에 대해 데이터의 관점에서 이 프로그램을 바라보며 데이터를 가지고 있는 객체들 간의 관계를 정의해서 프로그램을 만든다. (cf.C는 절차지향적 언어임)
SRP 단일책임의 원칙 ,OCP 개방폐쇄의 원칙, LSP 리스코브 치환의 원칙 , ISP 인터페이스 분리의 원칙, DIP 의존성역전의 원칙
다섯가지 원칙의 초성을 따서 SOLID원칙 라고부는데
이 원칙에 걸맞는 코드는 결합도가 느슨하고 응집력이 높은 코드가 됩니다 !
소프트웨어 공학에서 모듈화는 응고결저 라고 암기하죠?
결합도가 높을수록 응집도가 낮을수록 재사용성과 유지보수성이 높아집니다!
모든 상황에 이 원칙들을 적용하는데는 무리가 있을 수 있으나
가독성좋고 재사용성이 높은 코드를 짜기 위해서 알아야하는 SOLID 원칙에 대해 알아볼게여
https://www.youtube.com/watch?v=wGWrOpRdu40 를 참고하여 개념 정리 후 예제 코드를 만들어보았습니다.
이 코드는 이전 오버로딩 글에서 간단하게 만든 예시 코드인데요.
public class Car {
// 이 메서드는 주어진 색상과 유형에 따라 자동차의 기본 가격을 반환
private Integer car(String color, String type) {
int price;
switch (type.toLowerCase()) {
case "suv":
price = 30000;
break;
case "sedan":
price = 20000;
break;
case "coupe":
price = 25000;
break;
default:
price = 15000;
break;
}
// 색상에 따라 추가 비용이 발생
switch (color.toLowerCase()) {
case "red":
price += 1000;
break;
case "black":
price += 500;
break;
// 다른 색상에는 추가 비용이 없음
}
return price;
}
// 이 메서드는 색상만 주어질 경우 기본 유형인 "sedan"의 가격을 반환
private Integer car(String color) {
return car(color, "sedan");
}
public static void main(String[] args) {
Car myCar = new Car();
// SUV 타입의 빨간색 자동차 가격을 계산
Integer suvPrice = myCar.car("red", "suv");
System.out.println("SUV의 빨간색 자동차 가격: " + suvPrice);
// 기본 유형인 sedan의 검은색 자동차 가격을 계산
Integer sedanPrice = myCar.car("black");
System.out.println("Sedan의 검은색 자동차 가격: " + sedanPrice);
}
}
https://dev-emma-dev.tistory.com/28
[ Java ] Overloading 개념과 예시
: 메소드 이름을 같게하고, 파라미터(타입, 순서, 개수)를 달리해 여러 메소드를 정의하는 방법 제가 예시 코드를 만들어보았어요 car라는 이름의 메소드는 파라미터를 색상만 갖는 경우, 색상
dev-emma-dev.tistory.com
이 코드에 SOLID 원칙을 적용해서 수정해보도록할게요!
// SRP: Car 클래스는 자동차에 관련된 속성과 메서드만 담당합니다.
public class Car {
private String color;
private String type;
private int basePrice;
public Car(String color, String type, int basePrice) {
this.color = color;
this.type = type;
this.basePrice = basePrice;
}
public String getColor() {
return color;
}
public String getType() {
return type;
}
public int getBasePrice() {
return basePrice;
}
}
// OCP: 확장을 위해 CarPriceCalculator 인터페이스를 사용합니다.
interface CarPriceCalculator {
int calculatePrice(Car car);
}
// 구체적인 가격 계산 로직을 위한 기본 구현체
class BasicCarPriceCalculator implements CarPriceCalculator {
@Override
public int calculatePrice(Car car) {
int price = car.getBasePrice();
switch (car.getColor().toLowerCase()) {
case "red":
price += 1000;
break;
case "black":
price += 500;
break;
}
return price;
}
}
// LSP: SpecialCarPriceCalculator도 CarPriceCalculator의 일종입니다.
class SpecialCarPriceCalculator extends BasicCarPriceCalculator {
@Override
public int calculatePrice(Car car) {
int price = super.calculatePrice(car);
// 추가적인 가격 계산 로직
if ("special".equalsIgnoreCase(car.getType())) {
price += 2000;
}
return price;
}
}
// ISP: CarService는 필요한 메서드만 포함합니다.
interface CarService {
int getCarPrice(Car car);
}
class CarPriceService implements CarService {
private CarPriceCalculator calculator;
// DIP: CarPriceService는 추상화된 CarPriceCalculator에 의존합니다.
public CarPriceService(CarPriceCalculator calculator) {
this.calculator = calculator;
}
@Override
public int getCarPrice(Car car) {
return calculator.calculatePrice(car);
}
}
public class Main {
public static void main(String[] args) {
Car suv = new Car("red", "suv", 30000);
Car sedan = new Car("black", "sedan", 20000);
CarPriceCalculator basicCalculator = new BasicCarPriceCalculator();
CarPriceCalculator specialCalculator = new SpecialCarPriceCalculator();
CarService basicService = new CarPriceService(basicCalculator);
CarService specialService = new CarPriceService(specialCalculator);
System.out.println("SUV의 빨간색 자동차 가격: " + basicService.getCarPrice(suv));
System.out.println("Sedan의 검은색 자동차 가격: " + specialService.getCarPrice(sedan));
}
}
코드 설명과 함께 개념에 대해 적어보겠습니다
SRP 단일책임의 원칙 Single Responsibility Principle
: 클래스는 하나의 책임만 가져야한다
예를 들어 어떠한 물체가 움직임도 갖고 음성을 갖는다고 하면
이를 별도의 기능으로 만들고 별도의 컴포넌트로 오브젝트에 부착한다
움직임만 수정하고싶을 때 움직임 부분의 코드만 수정하면 될 수 있도록!
이런 단위로 분리해 사용하면 코드 가독성이 높아지며, 상속을 받아 사용하면 확장성도 좋아진다. 그러니 모듈식으로 여러부분에서 재사용성이 좋아진다.
코드 적용
→ Car 클래스는 자동차의 속성만을 담당하며, 가격 계산은 CarPriceCalculator 인터페이스와 그 구현체들에 의해 처리됩니다.
OCP 개방폐쇄의 원칙 Open Close Principle
: 클래스는 확장에 열려있어야 하며, 수정에는 닫혀있어야한다.
예를 들어 도형의 넓이를 구한다고 했을 때 도형별로 개별 함수가 존재해 매개변수를 각각 바꿔야 하는 대신 공용으로 쓸 수 있도록하여 계속 기능을 추가할수있도록 해야한다.
도형별로 개별함수가 존재해서 매개변수를 바꿔야하는데 이렇게 말고 공용으로 쓸 수 있게 설계해야한다.
사각형, 원이 아니라 모양이라는 추상 클래스로 만들고 오버라이드를 사용하면 된다.
차이는 도형이 추가되도 기존코드는 수정되지않고 새로운 클래스만 추가해도된다.
코드 적용
→ CarPriceCalculator 인터페이스를 사용하여 새로운 가격 계산 로직을 추가할 때 기존 코드를 수정할 필요 없이 새로운 클래스를 추가할 수 있습니다.
LSP 리스코브 치환의 원칙 The Liskov Substitution Principle
: 리스코브라는 사람이 만들어서 이름을 따옴. 상속 할 때 지켜야 하는 원칙으로 파생 클래스가 기본 클래스를 대체할 수 있어야 한다.
예를 들어 자동차,버스,기차 라는 교통수단이라는 클래스가 좌회전, 우회전 이라는 메소드이 있을 때
자동차나 버스 등은 다 이 기능이 적용되지만 기차의 경우는 오류가 발생한다.
좀 더 추상클래스를 간단하고 분류해서 만들어
상속하기보다 인터페이스를 이용해 '도로를 달리는 것', '레일을 달리는 것' 으로 나누는 것이다.
하위클래스는 어떤 경우에도 부모클래스를 대체할 수 있어야한다.
코드 적용
→ SpecialCarPriceCalculator는 BasicCarPriceCalculator를 확장하여 특별한 가격 계산 로직을 추가합니다.
ISP 인터페이스 분리의 원칙 Interface Sergregation Princicle
: 인터페이스는 한 번에 크게 사용하지 말고 작은 단위로 나누어 사용해야한다.
한 번에 모든 것들을 넣지말고 최대한 나눠,,, 이들을 조합해서 사용한다.
분리해서 쓸수록 코드간의 결합도가 낮아지고 수정이 용이해진다.
코드 적용
→ CarService 인터페이스는 가격 계산에 필요한 메서드만 포함합니다.
DIP 의존성역전의 원칙 Dependency Inversion Principle
: 고수준 모듈은 저소준 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야한다.
door 클래스에 open과 close 기능이 존재한다고 할 때,
switch클래스에서 door 을 열고 닫게 만들고, 여기 toggle을 만들어서 열고 닫게 할수잇는데
switch 클래스는 door를 직접적으로 알고있어 door만 열고 닫을 수 있다는 단점이있다.
switch는 door만 열고 닫는건 아니고, lignt 등 이외의 것들도 확성화 비활성화 할수잇어야한다.
그러므로 switch를 인터페이스로만들고 이 인터페이스에 활성 비활성화 함수를 생성하여
door에 이 인터페이스를 쓰면 기존 switch 클래스를 door에 직접 연결하지않고 인터페이스를 통해 연결하고
toggle에서 인터페이스 메소드를 사용하면된다.
코드 적용
→ CarPriceService는 CarPriceCalculator 인터페이스에 의존하여 구체적인 구현체와 분리됩니다.
'Chapter01 > Java' 카테고리의 다른 글
[Java] Stack / Queue 사용예시 (2) | 2025.03.07 |
---|---|
[ Java ] 이중해시맵 + 예시코드 (0) | 2024.06.17 |
[ java ] Stream_예시코드 (0) | 2024.05.28 |
[ Java ] 클래스 정의 (0) | 2024.03.13 |
[ Java ] Overloading 개념 + 예시코드 (1) | 2023.11.07 |