Refactoring (Martin Fowler)
코드 변경의 주요 목적 - 기능구현과 리팩토링
리팩토링 목적
처음에는 리팩토링 없이 하고, 비슷한 일을 세 번 이상 하게된다면 리팩토링 한다.
같은 로직/패턴이 3번 이상 반복되면 중복 제거하고 구조화하자는 의미
모든 코드에 대해 리팩토링할필요는 없고, 필요할 때 한다.
low quality code처럼 새로 작성하는게 더 쉬운 경우는 리팩토링을 포기한다.
리팩토링 기법들 또한 상충적 관계 존재
변수 추출하기 <-> 변수 인라인 하기
위임 숨기기 <-> 중개자 제거하기
🔍 “기존 코드가 원래 어떻게 작동했는지를 보호할 안전망 만들기”
이 테스트는 리팩토링 후에도 동작이 동일함을 보장하는 기준이 되는 TC 작성
특히 엣지 케이스나 부작용 있는 로직을 확실히 커버할 수 있도록 준비해야 함
🧪 목표: 리팩토링 후에도 "원래 하던 일은 그대로 한다"는 것을 검증할 수 있게 만들기
🔍 “어디가 문제인지 직관적으로 냄새를 맡는 단계”
중복된 코드
, 긴 함수
, 과도한 책임
, 네이밍 문제
, 의존성 꼬임
등
→ 대표적인 Code Smell 들을 기준으로 문제점 탐색
이 단계는 "문제가 생기진 않았지만, 나중에 분명 터질 수 있는 구조"를 선제적으로 잡아내는 데 초점
🧪 목표: 리팩토링의 타겟과 이유를 명확히 설정
🔧 “문제에 맞는 리팩토링 도구를 꺼내어 적용하는 작업”
Extract Method
, Rename
, Replace Temp with Query
, Introduce Parameter Object
등
→ 상황에 맞는 정형화된 리팩토링 패턴을 적용
성급하게 바꾸기보다 작은 단위로 천천히 개선하며 테스트 통과를 계속 확인
🧪 목표: 기능은 그대로, 구조만 개선
✅ “리팩토링 후에도 기존 테스트가 모두 통과하는지 확인”
Step 1에서 만든 테스트가 여전히 모두 통과해야 리팩토링이 성공적
이때 실패가 발생하면 → 어떤 리팩토링이 기존 동작을 깨트렸는지 추적해야 함
🧪 목표: 구조를 개선했지만 기능은 동일함을 증명
"기능은 돌아가지만, 유지보수성과 확장성이 나쁜 코드의 이상 징후"
Heuristic한 도구이다.
우선순위 | Code Smell 예시 | 비고 |
---|---|---|
⭐️ 필수 | 중복 코드, 긴 함수, 메시지 체인, 기능 편애 등 | 실습/실무에서 자주 접함 |
🔍 중급 | 가변 데이터, 거대한 클래스, 기본형 집착 | 구조 고민이 필요한 시점에서 중요 |
🧠 심화 | 추측성 일반화, 내부자 거래, 성의 없는 요소 등 | 리팩토링 수준이 올라갔을 때 고려 |
Code Smell | 설명 |
---|---|
중복 코드 | 유지보수 최악의 적. 한 군데만 바꿔도 여러 곳 영향을 줌 |
긴 함수 | 너무 많은 책임이 한 곳에 몰려 있음 |
기이한 이름 | 코드를 읽어도 무슨 일인지 감이 안 옴 |
메시지 체인 | A().B().C().D() → 의존성 꼬임의 원인 |
산탄총 수술 | 한 변경에 여러 파일, 클래스가 동시에 수정됨 |
기능 편애 | 어떤 메서드가 다른 클래스의 데이터에 집착 |
데이터 뭉치 | 늘 같이 다니는 변수 그룹 (→ 클래스로 묶을 수 있음) |
Code Smell | 설명 |
---|---|
가변 데이터 | 공유된 값이 쉽게 바뀔 수 있으면 위험 (side effect) |
임시 필드 | 어떤 조건에서만 쓰이는 필드 (→ 클래스 책임 모호) |
중개자 | 단순 전달만 하는 객체 (ex: getXXX().getYYY()) |
거대한 클래스 | 책임 분리가 안 된 덩치 클래스 |
상속 포기 | 자식 클래스가 부모 기능을 쓰지 않음 |
추측성 일반화 | 쓰이지도 않을 기능을 미리 만들어둠 |
기본형 집착 | int, string 등 primitive로 모든 걸 처리하려는 습관 |
Code Smell | 설명 |
---|---|
반복문 | 종종 map/filter/reduce 같은 고차 함수로 대체 가능 |
전역 데이터 | 테스트 어려움, side effect 유발 |
다른 인터페이스의 대안 클래스들 | 비슷한 기능인데 API 구조가 서로 달라 혼란 유발 |
성의 없는 요소 | 더 이상 쓰이지 않지만 남아있는 클래스/함수 |
내부자 거래 | 너무 깊게 남의 내부 구조를 엿봄 |
주석 | 나쁜 코드의 핑계로 쓰이는 경우 많음 (좋은 코드라면 설명이 필요 없음) |
→ 리팩토링 중 후속 개선을 위해 리뷰나 리팩터링 가이드라인에서 주로 언급되는 요소들
마땅한 이름이 떠오르지 않는다면, 더 근본적인 문제가 있을 가능성이 크다
def render_welcome():
print("Welcome")
print("Enjoy your stay")
def render_goodbye():
print("Goodbye")
print("Enjoy your stay")
↓
def render_footer():
print("Enjoy your stay")
def render_welcome():
print("Welcome")
render_footer()
def render_goodbye():
print("Goodbye")
render_footer()
def process_a():
do_first()
do_common()
do_last()
def process_b(): #순서 변경에 영향 없다면 a,b프로세스 동일.
do_common()
do_first()
do_last()
class Order:
def get_customer_info(self):
return self.customer.name + self.customer.address
class Invoice:
def get_customer_info(self):
return self.customer.name + self.customer.address
↓
class Customer:
def get_info(self):
return self.name + self.address
class Order:
def get_customer_info(self):
return self.customer.get_info()
class Invoice:
def get_customer_info(self):
return self.customer.get_info()
class Dog:
def speak(self):
print("Woof!")
class Cat:
def speak(self):
print("Woof!")
↓
class Animal:
def speak(self):
print("Woof!")
class Dog(Animal):
pass
class Cat(Animal):
pass
def calculate_total(order):
total = 0
for item in order.items:
if item.category == "sale":
total += item.price * 0.9
else:
total += item.price
if order.customer.is_vip:
total *= 0.95
return total
↓
def calculate_total(order):
subtotal = calculate_subtotal(order.items)
return apply_vip_discount(subtotal, order.customer)
def calculate_subtotal(items):
return sum(apply_discount(item) for item in items)
def apply_discount(item):
return item.price * 0.9 if item.category == "sale" else item.price
def apply_vip_discount(total, customer):
return total * 0.95 if customer.is_vip else total
if customer.is_vip:
apply_vip_discount()
elif customer.is_new:
apply_welcome_discount()
↓
def get_discount_strategy(customer):
if customer.is_vip:
return apply_vip_discount
if customer.is_new:
return apply_welcome_discount
return apply_standard_discount
tax = order.total * 0.1
final = order.total + tax
↓
def get_tax(order):
return order.total * 0.1
final = order.total + get_tax(order)
def register_user(name, age, gender, email, phone): ...
↓
def register_user(user_info): # user_info: UserInfo 객체
...
복잡한 계산 로직 → 클래스로 감싸서 run() 메서드 실행
ex) SalaryCalculator(employee).run()
✔️ 상태와 함수 분리, 테스트 용이
if-else가 타입/클래스에 따라 분기된다면 → 서브클래스로 분산
ex) if type == A: → class A(Employee)
동일 함수에 다른 로직이라면, 각 클래스에 정의된 같은 이름의 함수 호출로 분기 제거 가능. (클래스가 알아서 맞는 로직을 실행)
✔️ 조건문 제거, 책임 분리 명확
하나의 for문에서 여러 일을 동시에 하고 있다면 분리
✔️ 목적별로 나누면 가독성·최적화에 유리
register(name, age, gender, email, phone, country, address, ... )
Before
def register_user(name, age, gender, email, phone):
...
After
class UserInfo:
def __init__(self, name, age, gender, email, phone):
...
def register_user(user_info: UserInfo):
...
Before
def apply_discount(price, customer_type, purchase_date):
...
After
def apply_discount(order): # order 안에 모든 정보 포함
...
Before
def get_shipping_cost(weight):
...
After
def get_shipping_cost():
return self.weight * 0.1
Remove Flag Argument
)true/false
같은 플래그 인자는 조건문 중첩을 만들고 함수 책임을 흐리게 함예시
# Before
def print_msg(is_error):
if is_error:
print("ERROR")
else:
print("OK")
# After
def print_error(): print("ERROR")
def print_ok(): print("OK")
Combine Functions into Class
)예시
# Before
def login(): ...
def logout(): ...
# After
class Session:
def login(self): ...
def logout(self): ...
Global Data
)예시
# Before
CONFIG = {}
CONFIG["lang"] = "ko"
# After
class Config:
def __init__(self):
self._lang = "ko"
def set_lang(self, lang):
self._lang = lang
def get_lang(self):
return self._lang
✅ 테스트 용이, 추적 가능, 변경 안전성 증가
Mutable Data
)1. 여러 곳에서 직접 값을 바꾸는 경우
→ Encapsulate Variable
# Before
self.name = "sunyong"
# After
self._name = "sunyong"
def get_name(self): return self._name
def set_name(self, name): self._name = name
2. 같은 값을 여러 함수에서 계산하거나 조회/변경을 함께 하는 경우
→ Separate Query from Modifier
3. 데이터 역할이 섞여 명확하지 않을 경우
→ Extract Function
, Slide Statement
등으로 기능 분리
4. 하나의 변수에 다양한 역할이 섞여 있을 경우
→ Split Variable
5. 생성자 외의 setter 메서드로 변경되는 경우
→ Remove Setting Method
6. 파생값을 필드로 유지 중이라면?
→ Replace Derived Variable with Query
✅ 관련 동작이 분산된 경우
→ Combine Functions into Class
(응집도 향상)
→ Combine Functions into Transform
(변환 로직 통합)
✅ 참조보다는 복사가 안전한 경우
→ Change Reference to Value
# Before
profile = {"name": "sunyong"}
profile["name"] = "changed"
# After
class Profile:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
# set_name은 의도적으로 제공하지 않음 → 불변 설계
→ 데이터 흐름을 명확히 하고, 의도한 방식으로만 변경되게 만듬
Divergent Change
)예시
# Before
class Report:
def render(): ...
def send_email(): ...
def archive(): ...
# After
class ReportRenderer: ...
class ReportMailer: ...
class ReportArchiver: ...
✅ 변경에 유연한 구조, 각 기능에 집중
# 각기 다른 클래스에서 중복된 할인 정책을 갖고 있음
class Order:
def apply_discount(self):
return self.amount * 0.95
class Invoice:
def apply_discount(self):
return self.amount * 0.95
↓
class DiscountPolicy:
def apply(self, amount, customer):
return amount * 0.95 if customer.is_vip else amount
# 사용처에서는 하나의 정책 클래스만 호출
final_price = DiscountPolicy().apply(order.amount, order.customer)
def total_price(order): #order클래스에 있지 않으나 order의 데이터만으로 연산.
return order.quantity * order.unit_price
↓
class Order:
def total_price(self):
return self.quantity * self.unit_price
zip_code = order.get_customer().get_address().get_zip_code()
↓
# After
zip_code = order.get_zip_code()
# 내부 구현
class Order:
def get_zip_code(self):
return self.customer.address.zip_code
Data Clumps
)Introduce Parameter Object
– 관련 인자 하나로 묶기 Extract Class
– 함께 다니는 필드를 별도 클래스로 추출Primitive Obsession
)string
으로 처리 int
나 bool
로 처리 Introduce Value Object
→ 값을 나타내는 클래스를 도입해서, 의미 있는 타입으로 치환
Replace Primitive with Object
→ string
, int
→ Money
, Email
, PhoneNumber
등 구체적 클래스화
# Before
def register(name: str, email: str): ...
# After
class Email:
def __init__(self, value):
if '@' not in value:
raise ValueError("Invalid email")
self.value = value
def register(name: str, email: Email): ...
데이터의 의미, 검증, 동작을 하나의 타입으로 응집
type
, category
, role
같은 문자열/숫자 값(primitive)에 따라 동작을 분기할 때#Before
class Bird:
def get_speed(self):
if self.type == "European":
return 10
elif self.type == "African":
return 5
self.type
값에 따라 분기됨 → 타입 코드 사용"European"
, "African"
같은 문자열 type → 클래스 자체로 분리Bird
클래스를 상속받는 서브클래스를 만들어 분기 제거class Bird:
def get_speed(self):
raise NotImplementedError()
class European(Bird):
def get_speed(self):
return 10
class African(Bird):
def get_speed(self):
return 5
Repeated Switch Statements
)if
, switch
)가 여러 곳에 반복됨Replace Conditional with Polymorphism
Loops
)Replace Loop with Pipeline
– map/filter/reduce, 리스트 컴프리헨션 등 Extract Function
– 반복문 내부 작업을 목적별 함수로 분리# Before
names = []
for p in people:
if p.job == “programmer”:
names.append(p.name)
# After
names = list(.filter(lamda p : p.job == programmer", people)
Lazy Element
)Inline Function
– 너무 단순한 함수는 인라인 처리 Collapse Hierarchy
– 의미 없는 상속 구조는 통합 Remove Dead Code
– 쓰이지 않는 코드 제거# Before
class NameFormatter:
def format(self, user):
return user.name
# After
# 그냥 user.name 직접 사용
# Before
class Animal: pass
class Dog(Animal):
def bark(self): print("Woof")
# After
class Dog:
def bark(self): print("Woof")
✅ 기능이 없는 상위 클래스는 제거하여 계층 단순화
Extract Class
, Move Function + Move Field
→ 특정 상황에서만 쓰이는 필드를 담당하는 클래스를 따로 분리 / 그 필드가 더 적합한 객체로 책임 이
Introduce Null Object
→ 빈 동작/기본 동작을 하는 객체를 만들어 null 분기 제거
✅ 예시
# Before
class Booking:
def __init__(self, customer, is_premium):
self.customer = customer
if is_premium:
self.special_discount = 0.1
else:
self.special_discount = None
→ special_discount는 프리미엄 고객만 쓰는 필드
→ 일반 고객에겐 항상 None
# After1
class Booking:
def __init__(self, customer):
self.customer = customer
class PremiumBooking(Booking):
def __init__(self, customer):
super().__init__(customer)
self.special_discount = 0.1
→ 프리미엄 고객만 사용하는 필드는 서브클래스로 분리
Introduce Null Object
# Before
class Customer:
def __init__(self, name):
self.name = name
class Booking:
def __init__(self, customer=None):
self.customer = customer
def get_customer_name(self):
if self.customer is None:
return "Guest"
return self.customer.name
# After
class NullCustomer:
@property
def name(self):
return "Guest"
class Customer:
def __init__(self, name):
self.name = name
class Booking:
def __init__(self, customer):
self.customer = customer or NullCustomer()
def get_customer_name(self):
return self.customer.name
Message Chain
).
, .
, .
식으로 여러 단계로 이어지는 체인 형태Hide Delegate
– 대신 호출해주는 위임 메서드 제공 Move Method
– 더 적절한 객체로 기능을 옮김# Before
zip_code = order.get_customer().get_address().get_zip_code()
# After
class Order:
def get_zip_code(self):
return self.customer.address.zip_code
zip_code = order.get_zip_code()
✅ 내부 구조 은닉 + 호출부 간결화
항목 | 기능 편애 (Feature Envy ) | 메시지 체인 (Message Chain ) |
---|---|---|
문제의 본질 | 메서드가 다른 클래스의 데이터에 집착 | 호출자가 내부 구조를 너무 깊게 탐색 |
예시 | price = order.customer.discount_rate * 100 | order.get_customer().get_address().get_zip_code() |
주체 | 문제 있는 건 메서드의 위치 (옮겨야 함) | 문제 있는 건 호출 방식 (너무 탐색적임) |
주 리팩토링 | Move Method , 때때로 Hide Delegate | Hide Delegate , Move Method (간혹) |
Hide Delegate 의 역할 | 데이터에 집착하는 대신, 간접 접근 경로만 제공 | 체인 끊고 구조 은닉, 호출부 정리 |
Middle Man
)Remove Middle Man
(중개자 제거하기)# Before
class Order:
def get_customer_name(self):
return self.customer.get_name()
order.get_customer_name()
# After
order.customer.get_name()
Hide Delegate | Middle Man |
---|---|
호출자가 너무 구조를 따라감 → 위임시켜서 캡슐화 강화 | 위임만 하고 있는 메서드가 쓸모 없음 |
캡슐화를 위해 위임을 만드는 리팩토링 | 위임이 불필요하면 과감히 제거 |
"숨기는 위임" | "제거하는 위임" |
Inline Function
(함수 인라인하기)Replace Delegation with Inheritance
(위임을 상속으로 바꾸기)# Before
class Manager:
def __init__(self):
self.employee = Employee()
def get_salary(self):
return self.employee.get_salary()
# After
class Manager(Employee): # 상속으로 위임 제거
pass
Proxy, Decorator, Adapter 패턴처럼
기능이나 제어 흐름 목적이 있다면 중개자 유지하는 게 맞음
Insider Trading
)클래스 간 결합도(coupling)가 지나치게 높아진 상태. 구조가 취약해짐
문제 | 설명 |
---|---|
📉 캡슐화 깨짐 | 객체 내부 구조가 외부에 노출됨 |
🔁 변경 전파 | 하나 수정 시 여러 클래스 수정 필요 |
🧪 테스트 어려움 | 강한 의존 때문에 단위 테스트 어려움 |
🔀 책임 모호 | 행동을 누가 책임져야 하는지 불분명 |
# ❌ Before
class Account:
def __init__(self, transactions):
self.transactions = transactions
class BankSystem:
def calculate_total(account):
total = 0
for t in account.transactions:
if t.type == "deposit":
total += t.amount
return total
여기서 BankSystem은 Account의 내부 자료구조 (transactions)와 그 내부 구조 (type, amount)에 깊게 의존
앞으로 transactions 구조나 필드가 바뀌면 BankSystem도 다 같이 터짐
→ 💥 내부 구조가 외부 시스템에 직접 노출되고 조작됨
→ 따라서 캡슐화가 완전히 깨짐
→ = 내부자 거래
# ✅ After
class Account:
def __init__(self, transactions):
self.transactions = transactions
def total_deposits(self):
return sum(t.amount for t in self.transactions if t.type == "deposit")
class BankSystem:
def calculate_total(account):
return account.total_deposits()
✅ 이제 BankSystem
은 "total_deposits라는 기능"만 호출
→ 내부 구조는 몰라도 됨 → 캡슐화 성공
# ❌ Before (강한 상속 구조)
class Report:
def content(self):
return "Base content"
class ConfidentialReport(Report):
def content(self):
return "*** CONFIDENTIAL ***
" + super().content()
→ 부모 구조에 깊이 의존하고 있어 유연하지 않음
# ✅ After (위임 구조)
class Report:
def content(self):
return "Base content"
class ConfidentialWrapper:
def __init__(self, report):
self.report = report
def content(self):
return "*** CONFIDENTIAL ***
" + self.report.content()
→ 위임 구조로 바꾸면 결합도는 낮추고 유연성은 증가
리팩토링 | 설명 |
---|---|
Move Method , Move Field | |
Extract Class | 얽혀 있는 로직이 많다면 새 클래스로 분리 |
Hide Delegate | 중간 구조를 감추고 필요한 기능만 노출 |
Replace Subclass with Delegation | 상속 관계를 위임 구조로 바꿔 유연성 확보 |
공통 키워드 | 설명 |
---|---|
❗ 캡슐화 위반 | 객체 내부의 구조나 데이터를 외부에서 과도하게 조작함 |
🔄 책임 분리 실패 | 특정 로직의 책임이 애매한 위치에 있음 |
📉 구조적 결합도 ↑ | 하나 바꾸면 다른 데도 연쇄적으로 영향을 받음 |
💡 목표는 같음 | 각 객체가 “자기 일만 하게 만들자!” |
항목 | 메시지 체인 (Message Chain) | 기능 편애 (Feature Envy) | 내부자 거래 (Insider Trading) |
---|---|---|---|
핵심 문제 | 구조를 너무 따라감 | 메서드가 남의 데이터만 신경 씀 | 두 객체가 서로 너무 많이 앎 |
대표 증상 | a.b.c.d() 같이 체인이 길어짐 | this 는 일 안 하고, other 의 속성만 씀 | 한쪽이 다른 쪽 구조/행동을 깊게 침투 |
관계 구조 | 객체를 탐색하는 코드 | 잘못 위치한 메서드 | 클래스 간 결합도 자체가 높음 |
해결 방식 | Hide Delegate , Move Method | Move Method , Extract Method | Move Method , Extract Class , Delegate |
목표 | 구조 숨기기 | 메서드 위치 바로잡기 | 결합도 낮추고 역할 재배분 |
Large Class
)\하나의 클래스가 너무 많은 책임을 가지는 상태
→ 메서드, 필드, 기능이 과도하게 몰려 있어 SRP(단일 책임 원칙)을 위반함
변경 취약, 독립적 테스트 어려움, 재사용성 낮음
# ❌ Before
class Employee:
def __init__(self, name, age, salary, performance_reviews):
self.name = name
self.age = age
self.salary = salary
self.performance_reviews = performance_reviews
def calculate_bonus(self):
...
def save_to_database(self):
...
def render_employee_card(self):
...
Employee가 급여 계산, DB 저장,UI 렌더링까지…
→ 역할이 뒤죽박죽
# ✅ After
class Employee:
def __init__(self, name, age, salary, reviews):
...
def calculate_bonus(self):
...
class EmployeeRepository:
def save(employee):
...
class EmployeeCardRenderer:
def render(employee):
...
→ Extract Class
를 통해 데이터 처리, DB 저장, UI 렌더링을 역할별로 분리
기법 | 설명 |
---|---|
Extract Class | 한 클래스에 몰린 책임을 별도의 클래스로 나눔 |
Extract Superclass | 비슷한 클래스들 간 공통 기능을 상위 클래스로 이동 |
Replace Type Code with Subclasses | 타입 구분 필드를 클래스로 분리해 구조화 |
# ❌ Before
class Employee:
def __init__(self, name, emp_type):
self.name = name
self.type = emp_type # "ENGINEER", "MANAGER", "SALESPERSON"
def get_bonus(self):
if self.type == "ENGINEER":
return 1000
elif self.type == "MANAGER":
return 2000
elif self.type == "SALESPERSON":
return 1500
타입 코드로 분기하게 되면 왜 거대한 클래스일 수 있는가?
기준 | 설명 |
---|---|
📌 책임이 많음 | 타입별 보너스 로직을 모두 Employee 가 떠맡고 있음 |
📌 조건문이 반복됨 | type 코드가 바뀔 때마다 if self.type == ... 패턴이 증가 |
📌 응집도 낮음 | 각 조건문은 서로 관련 없음. 사실상 세 가지 다른 동작 |
📌 확장에 불리함 | 새로운 타입이 생기면 Employee 를 계속 고쳐야 함 (OCP 위반) |
→ 하나의 Employee 클래스가 너무 많은 책임을 떠안고 있어 결국 필요보다 거대한 클래스로 진화하게됨.
# ✅ After
class Employee:
def __init__(self, name):
self.name = name
def get_bonus(self):
raise NotImplementedError()
class Engineer(Employee):
def get_bonus(self):
return 1000
class Manager(Employee):
def get_bonus(self):
return 2000
class Salesperson(Employee):
def get_bonus(self):
return 1500
→ 조건문 제거 → 서브클래스가 책임을 개별적으로 가짐 → 유지보수 & 확장성 향상
Alternative Classes with Different Interfaces
)같은 기능을 수행하지만 인터페이스가 달라서
서로 바꿔 끼우기 어렵고 일관성이 없는 클래스들이 존재할 때 발생하는 스멜
# ❌ Before
class ImageDownloader:
def download(self, url):
...
class PictureFetcher:
def fetch(self, uri):
...
# ✅ After
class ImageSource(ABC):
def fetch_image(self, path):
...
class ImageDownloader(ImageSource):
def fetch_image(self, path):
...
class PictureFetcher(ImageSource):
def fetch_image(self, path):
...
→ 인터페이스 통합으로 교체 가능성과 일관성 확보
기법 | 설명 |
---|---|
함수 선언 바꾸기 (Change Function Declaration), 옮기기 | 공통된 메서드 구조로 통합(unify interface) |
Extract Superclass | 상위 클래스나 인터페이스로 정리 |
Adapter Pattern | 기존 구조를 유지하되 어댑터로 감싸 통일 |
필드(데이터)만 있고, 동작(메서드)가 거의 없는 클래스(책임x)
실제 로직은 다른 클래스에서 이 데이터를 가져와 처리
→ 데이터와 동작이 분리
기법 | 설명 |
---|---|
Encapsulate Field | 필드를 직접 노출하지 말고 캡슐화 |
Move Function | 데이터를 사용하는 함수는 그 데이터를 가진 객체로 옮기기 |
Refused Bequest
)형식적 상속은 있지만 실제 기능적 상속은 없는 상태( 실제 상속된 필드나 메서드를 거의 사용하지 않거나 의미를 왜곡하거나 오버라이드해서 깨트리는 경우)
상황 | 리팩토링 |
---|---|
일부 자식만 사용하는 기능 | Push Down Method/Field |
자식이 인터페이스 따르지 않음 | Replace Superclass with Delegation |
공통 로직만 뽑고 싶을 때 | Extract Superclass |
class Bird:
def fly(self): ...
def swim(self): ...
class Eagle(Bird): pass
class Penguin(Bird):
def fly(self): raise Exception("Penguin can't fly")
class Bird:
def fly(self): ...
class Eagle(Bird): pass
class Penguin:
def swim(self): ...
✅ Penguin은 더 이상 Bird가 아님 → 구조 명확화
class DataStructure:
def add(self): ...
def remove(self): ...
class Stack(DataStructure):
def add(self): ...
def remove(self): ...
class Stack:
def __init__(self):
self._internal = DataStructure()
def push(self): return self._internal.add()
def pop(self): return self._internal.remove()
✅ 상속보다 유연하며, 의미에 맞는 구조로 전환