🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [19]일차
🚀19일차에는 객체지향원칙과 객체지향설계원칙에 대해 배울 수 있었다.
객체지향원칙 핵심개념으로 정보처리기사 공부할때 외워놨던 "캡상추다"를 다시 접할 수 있었다.
캡슐화, 상속, 추상화, 다형성은 회고 초반에 진행했던 실습들과 연관이 많이 되어있어서 개념의 이해가 더 쉬웠다.
객체지향설계원칙으로는 SOLID 가 있는데 이또한 이전에 공부할때는 약어만 외웠었지만 실제로 어떤 일들을 하는지를
예제코드와 함께 공부해보니 왜 사용하는 것인지, 어느때 사용해야하는지를 익힐 수 있었다.
객체지향원칙 OOP
핵심개념
- 캡상추다 ➡️ 캡슐화 / 상속 / 추상화 / 다형성
추상화
- 복잡한 시스템이나 객체를 “단순화 하여 핵심적인 속성이나 기능만 나타냄”
- 구체적인 세부구현은 감추고, 필요한 “인터페이스나 기능만 노출”
- Payment 인터페이스 정의 ➡️CreditCard, PayPal 등처럼 결제방법을 오버라이딩 구현하여 “구체성을 숨김”
캡슐화
- 객체의 속성과 메소드를 하나의 논리적 단위로 묶음
(ex. computer 클래스 안
keyboard() 메소드, monitor 메소드, private boolean togglePower 필드, private String userName 필드 등) - 객체의 속성을 외부에서는 이 객체가 제공하는 메소드를 통해서만 접근하도록함
- 내부 구현을 감추고(정보 은닉), 필요한 인터페이스만 노출
ex. User 클래스 private int password 를 getPassword()를 통해서만 접근가능, setPassword() 는 암호화 처리담당
상속
- 상위 부모 클래스의 속성과 메소드를 하위 자식 클래스에서 물려받아 사용
- 필요부분 오버라이딩 및 확장 가능
다형성
- 같은 메소드 호출이라도 객체의 실제 타입에 따라 “다른 동작” 수행
- 오버라이딩, 오버로딩 등을 통해 다양한 형태로 동작
- ex. Animal타입으로 speak() 호출 → Dog객체면 “멍멍”, Cat 객체면 “야옹”
❓설계원칙이 중요한 이유
➡️코드의 유지보수성 높이기 (각 객체의 책임이 분명, 수정 범위가 최소화)
➡️확장성과 유연성 확보 (변화가능한 지점을 미리 추상화하여 기능추가로 인한 코드 수정 최소화)
➡️의존성과 결합도 줄이기 (재사용성 향상, 모듈간 결합도 낮추고 응집도 높이기)
객체지향설계원칙 5가지
SOLID
- SRP / OCP / LSP / ISP / DIP
SRP
- Single Responsibility Principle
- 클래스나 모듈은 오직 하나의 책임만 가지기
- 여러 역할 수행으로 인한 변경범위 증가와 결합도가 높아짐을 방지
- “별도의 클래스로 분리” ➡️오직 하나의 책임만 가질 수 있게되어 결합도가 높아짐을 방지
- 하나의 클래스에는 하나의 책임만 가질 수 있도록 설계
// User 클래스
// 1. SRP원칙 적용 하기 전
public void setPassword(String password){
if(password != null && password.length() >= MAX_PW_LENGTH){
this.password = password;
}else{
throw new IllegalArgumentException("유효하지 않은 비밀번호입니다!");
}
}
// User클래스
// 2. SRP원칙 적용한 후
public void setPasswordValidator(String password){
if(PasswordValidator.isValid(password)){
this.password = password;
}
else{
throw new IllegalArgumentException("유효하지 않은 비밀번호입니다!");
}
}
// Password 검증기 클래스 분리
class PasswordValidator{
public static boolean isValid(String password){
return password != null && password.length() >= 6;
}
}
- PasswordValidator와 같은 클래스로 별도로 분리하여 메소드를 호출하는 방식으로 사용


🚀실습 - SRP Game 만들기
// GameSystem클래스
public static void printUserInfo(String name, int level){
// ... 정보출력 파트...
try(FileWriter writer = new FileWriter("src/sample/SRPGameUser.txt")){
writer.write("유저 : " + name);
writer.write("레벨 : " + level);
}catch(Exception e){
System.out.println(e + "올바르지 않은 유저와 레벨입니다.");
}
System.exit(0);
}
- GameSystem 클래스 : 유저가 접속을 종료하면 유저가 플레이한 정보를 SRPGameUser.txt 파일에 쓸 수 있도록 구현
// EventManager 클래스
public static int setEventMonth(){
gs.setMonth(12);
return gs.getMonth();
}
public static void printAllEvent(){
int month = setEventMonth();
System.out.println("=====[" + month + "]월의 이벤트=====");
// ...이벤트 정보 출력 파트...
}
- EventManager 클래스 : GameSystem클래스에서 Month정보를 관리하므로 Month정보를 Setter로 갱신
- printAllEvent() : 갱신한 Month정보로 이벤트 목록을 출력
// 유저 클래스
// 유저 접속 종료
public void userExit(){
SRPGameSystem.printUserInfo(name, level);
}
// 현재 진행중인 이벤트 확인하기
public void checkEvent(){
SRPEventManager.printAllEvent();
}
- 게임을 진행하는 유저가 (접속 종료) 및 (이벤트 확인)을 호출할 권한이 있으므로
각 클래스의 메소드를 유저에서 호출할 수 있도록 구현


OCP
- Open-Closed Principle
- 확장에는 열려있고, 수정에는 닫혀있기
- 새 기능 추가시 기존 코드를 크게 수정하지 않음
🚀실습 - 축구선수 출력 : OCP
interface FootballLeagues{
void playerInfo(String name, String team);
}
class PremierLeague implements FootballLeagues{
private String leagueName;
public PremierLeague(String leagueName) {
this.leagueName = leagueName;
}
@Override
public void playerInfo(String name, String team) {
System.out.println(leagueName + " [" + team + "]의 - " + name);
}
}
FootballLeagues epl = new PremierLeague("PremierLeague");
FootballLeagues spain = new Laliga("Laliga");
epl.playerInfo("Van den berg", "Brentford");
spain.playerInfo("Aspas", "Celta de Vigo");

- 다른 리그를 추가하더라도 FootballLeagues를 구현하는 방식으로 기존 코드를 변경하지 않고 추가가 가능
➡️확장에는 열려있고 수정에는 닫혀있음
🚀실습 - 축구선수 출력 : OCP (리팩토링)
- 인터페이스를 구현하는 “연결”클래스를 만든다.
- 연결클래스를 main메소드에서 객체로 생성하여 new생성자를 통해 인자를 넣어
리그 별로 데이터를 불러올 수 있도록 리팩토링
// 연결 클래스 추가
class LeaguesType{
public void process(FootballLeagues football, String name, String team){
football.playerInfo(name, team);
}
}
LeaguesType league = new LeaguesType();
league.process(new PremierLeague("PremierLeague"), "Baines", "Everton FC");
league.process(new Laliga("Laliga"), "Ferran", "FC Barcelona");

- 연결클래스의 객체를 생성하여, new생성자로 인자들을 넣은 후 출력 확인
LSP
- Liskov Substitution Principle
- 자식클래스는 부모클래스의 행위를 깨뜨리지 않고 대체가능해야함
- 상속구조에서의 다형성을 보장
▶️실습 - 직사각형과 정사각형 (리스코프 치환 전)
class Square extends Rectangle{
public Square(int side){
super(side, side);
}
}
- Rectangle 클래스 : 직사각형 클래스로 필드 width, height는 Getter, Setter메소드가 있다.
- Square 클래스 : 정사각형 클래스로 Rectangle클래스를 상속받고
super로 보낼때 (side, side)로 인자를 보낼 수 있을 것이다. - 정사각형.setWidth()으로 값을 변경할때 정사각형이기때문에 두 변(side) 다 바뀌어야하는데,
width값만 바뀌어서 예상한 결과가 나오지 않는다 ➡️리스코프 치환이 되지 않은 경우이다.
▶️실습 - 직사각형과 정사각형 (리스코프 치환 후)
@Override
public void setHeight(int height) {
super.height = height;
super.width = height;
}
@Override
public void setWidth(int width) {
super.height = width;
super.width = width;
}
- square클래스의 Setter메소드에서 입력값(height or width)에 따라 들어온 값으로
두 변(side) 모두 갱신할 수 있도록 수정
▶️실습 - 직사각형과 정사각형 (리팩토링 - 인터페이스 사용)
- 직사각형과 정사각형 클래스가 Shape라는 인터페이스를 구현하도록 함
interface Shape{
int area();
}
// ... Rectangle 클래스 파트
class Square2 implements Shape{
private int side;
public Square2(int side) {
this.side = side;
}
public void setSide(int side) {
this.side = side;
}
@Override
public int area() {
return side * side;
}
}
//Main메소드
Shape square = new Square2(5);
System.out.println("정사각형 넓이 : " + square.area());
- 인터페이스의 area() : 넓이 구하기 메소드를 이용하여 그 메소드를 인터페이스를 구현한 클래스에서
오버라이딩하여 사용할 수 있게 만드는 것이 상속으로 만드는 것보다 좋은 설계일 수 있다.
▶️실습 - 자동차 주행 프로그램 (추상클래스 사용)
- 공통된 부분을 추상클래스 상위 객체로 만든다.
- 이 추상클래스를 상속받아서 사용하도록 하면 메소드를 강제할 수 있고
그 메소드는 오버라이딩하여 구현부를 달리할 수 있을 것 - 인터페이스는 추상메소드만 가질 수 있지만
추상클래스는 추상메소드 외 다른 것들도 가질 수 있으므로 추상클래스를 상속받아서 구현
abstract class Vehicle{
abstract void drive();
}
// ... Car클래스 구현 파트
- 추상클래스로 drive() 추상메소드를 선언
class ElectricCar extends Vehicle{
private int battery;
public ElectricCar(int battery) {
this.battery = battery;
}
@Override
public void drive() {
while(battery > 0){
battery--;
System.out.println("주행 중... 남은 배터리량 : " + battery);
}
System.out.println("배터리가 없어 주행이 불가능합니다.");
}
}
- Car클래스의 drive()는 if문으로 오버라이딩해보고, ElectricCar클래스의 drive()는 while문으로 오버라이딩
- 생성자로 배터리 값을 받고, 그 배터리를 필드 battery에 갱신시켜서 필드 battery로 출력문을 수행

ISP
- Interface Segration Principle
- 클라이언트는 자신이 사용하지 않는 메소드에 의존하지 않아야함
- 인터페이스 세분화로 “불필요한 의존 줄이기”
▶️실습 - 주문 시스템 : ISP (적용 전)
interface OrderService{
void placeOrder(String item);
void cancelOrder(String orderId);
}
class OrderClient{
private final OrderService orderService;
public OrderClient(OrderService orderService) {
this.orderService = orderService;
}
public void createNewOrder(){
orderService.placeOrder("book");
}
}
- 인터페이스 안에 두개의 기능을 가진 메소드가 있다.
- 하지만 인터페이스가 바뀌었을때 이 인터페이스를 의존하고 있는 클래스들은
인터페이스의 수정으로 인해 사용하지 않는 인터페이스의 메소드가 있더라도 영향을 받을 수 있다. - 주로 인터페이스의 설계가 모호하거나 너무 많은 기능을 가지고 있었을때 발생하는 경우이다.
- 따라서 인터페이스 안에 A기능과 B기능을 같이 정의하는 것이 아닌
각각의 인터페이스로 정의해두는 것으로 해결할 수 있는데 이를 “인터페이스 분리 원칙” ISP 라고 한다. - 하나의 인터페이스가 너무 많은 기능을 가지고 있는 것 = ISP(인터페이스분리원칙)
이는 하나의 클래스가 한 책임만 지도록해야한다는 SRP (단일책임원칙)과 유사하다.
▶️실습 - 주문 시스템 : ISP (적용 후)
interface OrderOperations{
void placeOrder(String item);
}
interface CancelOperations{
void cancelOrder(String orderId);
}
- 인터페이스를 분리
class OnlineOrderService implements CancelOperations, OrderOperations{
@Override
public void cancelOrder(String orderId) {
System.out.print("[주문취소번호 : " + orderId + "] - ");
System.out.println("주문이 취소되었습니다.");
}
@Override
public void placeOrder(String item) {
System.out.println("[" + item + "] 주문완료!");
}
}
public static void main(String[] args) {
// 두가지 인터페이스를 구현한 서비스 클래스를 선언
OnlineOrderService service = new OnlineOrderService();
// service는 두가지 인터페이스를 포함하므로 그 중 OrderOperations타입이 인자로 전달된다.
OrderClient client = new OrderClient(service);
client.createNewOrder();
service.cancelOrder("NO241227");
}
- OnlineOrderService 와 OrderClient를 생성 후 OrderClient의 매개변수로 OnlineOrderService의 객체를 넣어주었다.
- OnlineOrderService는 두 가지 인터페이스 (OrderOperations, CancelOperations)를 포함하고 있으므로
- OrderClient가 생성자로 가지는 타입인 OrderOperations를 받아서 메소드를 수행할 것이다.

DIP
- Dependency Inversion Principle
- 상위 추상 모듈은 하위 구현 모듈에 의존하지 않도록함
- 추상화에 의존하여 변경에 유연하게 대응
💡각 원칙 별로 실습을 진행하면서 원칙들이 조금씩 다르면서도 조금은 유사한 부분도 발견할 수 있었다.
예제 이외에도 비슷한 상황의 샘플데이터가 없을까 찾아보다가 아이디어가 생기는대로 실습을 진행해볼 수도 있었다.
오늘 회고에서 확장에는 열려있어야하고 수정에는 닫혀있어야한다는 OCP 원칙이 가장 기억에 남는다.
LSP원칙과 ISP원칙의 경우에는 원칙을 고려하여 설계하면서 더 복잡해진 것 같다는 느낌이 들기도했지만
LSP원칙에서 정사각형과 직사각형의 예외가 발생하는 상황을 보니 LSP원칙이 객체지향설계원칙이 왜 중요한지를 알 수 있었다.🚀
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_21일차_''프론트엔드 시작" (2) | 2024.12.31 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_20일차_''디자인패턴" (2) | 2024.12.30 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_18일차_''스레드 Thread" (0) | 2024.12.26 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_17일차_''Java IO" (1) | 2024.12.24 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_16일차_''컬렉션 프레임워크" (0) | 2024.12.23 |
🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [19]일차
🚀19일차에는 객체지향원칙과 객체지향설계원칙에 대해 배울 수 있었다.
객체지향원칙 핵심개념으로 정보처리기사 공부할때 외워놨던 "캡상추다"를 다시 접할 수 있었다.
캡슐화, 상속, 추상화, 다형성은 회고 초반에 진행했던 실습들과 연관이 많이 되어있어서 개념의 이해가 더 쉬웠다.
객체지향설계원칙으로는 SOLID 가 있는데 이또한 이전에 공부할때는 약어만 외웠었지만 실제로 어떤 일들을 하는지를
예제코드와 함께 공부해보니 왜 사용하는 것인지, 어느때 사용해야하는지를 익힐 수 있었다.
객체지향원칙 OOP
핵심개념
- 캡상추다 ➡️ 캡슐화 / 상속 / 추상화 / 다형성
추상화
- 복잡한 시스템이나 객체를 “단순화 하여 핵심적인 속성이나 기능만 나타냄”
- 구체적인 세부구현은 감추고, 필요한 “인터페이스나 기능만 노출”
- Payment 인터페이스 정의 ➡️CreditCard, PayPal 등처럼 결제방법을 오버라이딩 구현하여 “구체성을 숨김”
캡슐화
- 객체의 속성과 메소드를 하나의 논리적 단위로 묶음
(ex. computer 클래스 안
keyboard() 메소드, monitor 메소드, private boolean togglePower 필드, private String userName 필드 등) - 객체의 속성을 외부에서는 이 객체가 제공하는 메소드를 통해서만 접근하도록함
- 내부 구현을 감추고(정보 은닉), 필요한 인터페이스만 노출
ex. User 클래스 private int password 를 getPassword()를 통해서만 접근가능, setPassword() 는 암호화 처리담당
상속
- 상위 부모 클래스의 속성과 메소드를 하위 자식 클래스에서 물려받아 사용
- 필요부분 오버라이딩 및 확장 가능
다형성
- 같은 메소드 호출이라도 객체의 실제 타입에 따라 “다른 동작” 수행
- 오버라이딩, 오버로딩 등을 통해 다양한 형태로 동작
- ex. Animal타입으로 speak() 호출 → Dog객체면 “멍멍”, Cat 객체면 “야옹”
❓설계원칙이 중요한 이유
➡️코드의 유지보수성 높이기 (각 객체의 책임이 분명, 수정 범위가 최소화)
➡️확장성과 유연성 확보 (변화가능한 지점을 미리 추상화하여 기능추가로 인한 코드 수정 최소화)
➡️의존성과 결합도 줄이기 (재사용성 향상, 모듈간 결합도 낮추고 응집도 높이기)
객체지향설계원칙 5가지
SOLID
- SRP / OCP / LSP / ISP / DIP
SRP
- Single Responsibility Principle
- 클래스나 모듈은 오직 하나의 책임만 가지기
- 여러 역할 수행으로 인한 변경범위 증가와 결합도가 높아짐을 방지
- “별도의 클래스로 분리” ➡️오직 하나의 책임만 가질 수 있게되어 결합도가 높아짐을 방지
- 하나의 클래스에는 하나의 책임만 가질 수 있도록 설계
// User 클래스
// 1. SRP원칙 적용 하기 전
public void setPassword(String password){
if(password != null && password.length() >= MAX_PW_LENGTH){
this.password = password;
}else{
throw new IllegalArgumentException("유효하지 않은 비밀번호입니다!");
}
}
// User클래스
// 2. SRP원칙 적용한 후
public void setPasswordValidator(String password){
if(PasswordValidator.isValid(password)){
this.password = password;
}
else{
throw new IllegalArgumentException("유효하지 않은 비밀번호입니다!");
}
}
// Password 검증기 클래스 분리
class PasswordValidator{
public static boolean isValid(String password){
return password != null && password.length() >= 6;
}
}
- PasswordValidator와 같은 클래스로 별도로 분리하여 메소드를 호출하는 방식으로 사용


🚀실습 - SRP Game 만들기
// GameSystem클래스
public static void printUserInfo(String name, int level){
// ... 정보출력 파트...
try(FileWriter writer = new FileWriter("src/sample/SRPGameUser.txt")){
writer.write("유저 : " + name);
writer.write("레벨 : " + level);
}catch(Exception e){
System.out.println(e + "올바르지 않은 유저와 레벨입니다.");
}
System.exit(0);
}
- GameSystem 클래스 : 유저가 접속을 종료하면 유저가 플레이한 정보를 SRPGameUser.txt 파일에 쓸 수 있도록 구현
// EventManager 클래스
public static int setEventMonth(){
gs.setMonth(12);
return gs.getMonth();
}
public static void printAllEvent(){
int month = setEventMonth();
System.out.println("=====[" + month + "]월의 이벤트=====");
// ...이벤트 정보 출력 파트...
}
- EventManager 클래스 : GameSystem클래스에서 Month정보를 관리하므로 Month정보를 Setter로 갱신
- printAllEvent() : 갱신한 Month정보로 이벤트 목록을 출력
// 유저 클래스
// 유저 접속 종료
public void userExit(){
SRPGameSystem.printUserInfo(name, level);
}
// 현재 진행중인 이벤트 확인하기
public void checkEvent(){
SRPEventManager.printAllEvent();
}
- 게임을 진행하는 유저가 (접속 종료) 및 (이벤트 확인)을 호출할 권한이 있으므로
각 클래스의 메소드를 유저에서 호출할 수 있도록 구현


OCP
- Open-Closed Principle
- 확장에는 열려있고, 수정에는 닫혀있기
- 새 기능 추가시 기존 코드를 크게 수정하지 않음
🚀실습 - 축구선수 출력 : OCP
interface FootballLeagues{
void playerInfo(String name, String team);
}
class PremierLeague implements FootballLeagues{
private String leagueName;
public PremierLeague(String leagueName) {
this.leagueName = leagueName;
}
@Override
public void playerInfo(String name, String team) {
System.out.println(leagueName + " [" + team + "]의 - " + name);
}
}
FootballLeagues epl = new PremierLeague("PremierLeague");
FootballLeagues spain = new Laliga("Laliga");
epl.playerInfo("Van den berg", "Brentford");
spain.playerInfo("Aspas", "Celta de Vigo");

- 다른 리그를 추가하더라도 FootballLeagues를 구현하는 방식으로 기존 코드를 변경하지 않고 추가가 가능
➡️확장에는 열려있고 수정에는 닫혀있음
🚀실습 - 축구선수 출력 : OCP (리팩토링)
- 인터페이스를 구현하는 “연결”클래스를 만든다.
- 연결클래스를 main메소드에서 객체로 생성하여 new생성자를 통해 인자를 넣어
리그 별로 데이터를 불러올 수 있도록 리팩토링
// 연결 클래스 추가
class LeaguesType{
public void process(FootballLeagues football, String name, String team){
football.playerInfo(name, team);
}
}
LeaguesType league = new LeaguesType();
league.process(new PremierLeague("PremierLeague"), "Baines", "Everton FC");
league.process(new Laliga("Laliga"), "Ferran", "FC Barcelona");

- 연결클래스의 객체를 생성하여, new생성자로 인자들을 넣은 후 출력 확인
LSP
- Liskov Substitution Principle
- 자식클래스는 부모클래스의 행위를 깨뜨리지 않고 대체가능해야함
- 상속구조에서의 다형성을 보장
▶️실습 - 직사각형과 정사각형 (리스코프 치환 전)
class Square extends Rectangle{
public Square(int side){
super(side, side);
}
}
- Rectangle 클래스 : 직사각형 클래스로 필드 width, height는 Getter, Setter메소드가 있다.
- Square 클래스 : 정사각형 클래스로 Rectangle클래스를 상속받고
super로 보낼때 (side, side)로 인자를 보낼 수 있을 것이다. - 정사각형.setWidth()으로 값을 변경할때 정사각형이기때문에 두 변(side) 다 바뀌어야하는데,
width값만 바뀌어서 예상한 결과가 나오지 않는다 ➡️리스코프 치환이 되지 않은 경우이다.
▶️실습 - 직사각형과 정사각형 (리스코프 치환 후)
@Override
public void setHeight(int height) {
super.height = height;
super.width = height;
}
@Override
public void setWidth(int width) {
super.height = width;
super.width = width;
}
- square클래스의 Setter메소드에서 입력값(height or width)에 따라 들어온 값으로
두 변(side) 모두 갱신할 수 있도록 수정
▶️실습 - 직사각형과 정사각형 (리팩토링 - 인터페이스 사용)
- 직사각형과 정사각형 클래스가 Shape라는 인터페이스를 구현하도록 함
interface Shape{
int area();
}
// ... Rectangle 클래스 파트
class Square2 implements Shape{
private int side;
public Square2(int side) {
this.side = side;
}
public void setSide(int side) {
this.side = side;
}
@Override
public int area() {
return side * side;
}
}
//Main메소드
Shape square = new Square2(5);
System.out.println("정사각형 넓이 : " + square.area());
- 인터페이스의 area() : 넓이 구하기 메소드를 이용하여 그 메소드를 인터페이스를 구현한 클래스에서
오버라이딩하여 사용할 수 있게 만드는 것이 상속으로 만드는 것보다 좋은 설계일 수 있다.
▶️실습 - 자동차 주행 프로그램 (추상클래스 사용)
- 공통된 부분을 추상클래스 상위 객체로 만든다.
- 이 추상클래스를 상속받아서 사용하도록 하면 메소드를 강제할 수 있고
그 메소드는 오버라이딩하여 구현부를 달리할 수 있을 것 - 인터페이스는 추상메소드만 가질 수 있지만
추상클래스는 추상메소드 외 다른 것들도 가질 수 있으므로 추상클래스를 상속받아서 구현
abstract class Vehicle{
abstract void drive();
}
// ... Car클래스 구현 파트
- 추상클래스로 drive() 추상메소드를 선언
class ElectricCar extends Vehicle{
private int battery;
public ElectricCar(int battery) {
this.battery = battery;
}
@Override
public void drive() {
while(battery > 0){
battery--;
System.out.println("주행 중... 남은 배터리량 : " + battery);
}
System.out.println("배터리가 없어 주행이 불가능합니다.");
}
}
- Car클래스의 drive()는 if문으로 오버라이딩해보고, ElectricCar클래스의 drive()는 while문으로 오버라이딩
- 생성자로 배터리 값을 받고, 그 배터리를 필드 battery에 갱신시켜서 필드 battery로 출력문을 수행

ISP
- Interface Segration Principle
- 클라이언트는 자신이 사용하지 않는 메소드에 의존하지 않아야함
- 인터페이스 세분화로 “불필요한 의존 줄이기”
▶️실습 - 주문 시스템 : ISP (적용 전)
interface OrderService{
void placeOrder(String item);
void cancelOrder(String orderId);
}
class OrderClient{
private final OrderService orderService;
public OrderClient(OrderService orderService) {
this.orderService = orderService;
}
public void createNewOrder(){
orderService.placeOrder("book");
}
}
- 인터페이스 안에 두개의 기능을 가진 메소드가 있다.
- 하지만 인터페이스가 바뀌었을때 이 인터페이스를 의존하고 있는 클래스들은
인터페이스의 수정으로 인해 사용하지 않는 인터페이스의 메소드가 있더라도 영향을 받을 수 있다. - 주로 인터페이스의 설계가 모호하거나 너무 많은 기능을 가지고 있었을때 발생하는 경우이다.
- 따라서 인터페이스 안에 A기능과 B기능을 같이 정의하는 것이 아닌
각각의 인터페이스로 정의해두는 것으로 해결할 수 있는데 이를 “인터페이스 분리 원칙” ISP 라고 한다. - 하나의 인터페이스가 너무 많은 기능을 가지고 있는 것 = ISP(인터페이스분리원칙)
이는 하나의 클래스가 한 책임만 지도록해야한다는 SRP (단일책임원칙)과 유사하다.
▶️실습 - 주문 시스템 : ISP (적용 후)
interface OrderOperations{
void placeOrder(String item);
}
interface CancelOperations{
void cancelOrder(String orderId);
}
- 인터페이스를 분리
class OnlineOrderService implements CancelOperations, OrderOperations{
@Override
public void cancelOrder(String orderId) {
System.out.print("[주문취소번호 : " + orderId + "] - ");
System.out.println("주문이 취소되었습니다.");
}
@Override
public void placeOrder(String item) {
System.out.println("[" + item + "] 주문완료!");
}
}
public static void main(String[] args) {
// 두가지 인터페이스를 구현한 서비스 클래스를 선언
OnlineOrderService service = new OnlineOrderService();
// service는 두가지 인터페이스를 포함하므로 그 중 OrderOperations타입이 인자로 전달된다.
OrderClient client = new OrderClient(service);
client.createNewOrder();
service.cancelOrder("NO241227");
}
- OnlineOrderService 와 OrderClient를 생성 후 OrderClient의 매개변수로 OnlineOrderService의 객체를 넣어주었다.
- OnlineOrderService는 두 가지 인터페이스 (OrderOperations, CancelOperations)를 포함하고 있으므로
- OrderClient가 생성자로 가지는 타입인 OrderOperations를 받아서 메소드를 수행할 것이다.

DIP
- Dependency Inversion Principle
- 상위 추상 모듈은 하위 구현 모듈에 의존하지 않도록함
- 추상화에 의존하여 변경에 유연하게 대응
💡각 원칙 별로 실습을 진행하면서 원칙들이 조금씩 다르면서도 조금은 유사한 부분도 발견할 수 있었다.
예제 이외에도 비슷한 상황의 샘플데이터가 없을까 찾아보다가 아이디어가 생기는대로 실습을 진행해볼 수도 있었다.
오늘 회고에서 확장에는 열려있어야하고 수정에는 닫혀있어야한다는 OCP 원칙이 가장 기억에 남는다.
LSP원칙과 ISP원칙의 경우에는 원칙을 고려하여 설계하면서 더 복잡해진 것 같다는 느낌이 들기도했지만
LSP원칙에서 정사각형과 직사각형의 예외가 발생하는 상황을 보니 LSP원칙이 객체지향설계원칙이 왜 중요한지를 알 수 있었다.🚀
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_21일차_''프론트엔드 시작" (2) | 2024.12.31 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_20일차_''디자인패턴" (2) | 2024.12.30 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_18일차_''스레드 Thread" (0) | 2024.12.26 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_17일차_''Java IO" (1) | 2024.12.24 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_16일차_''컬렉션 프레임워크" (0) | 2024.12.23 |