🐢 객체지ν–₯ 개발 5λŒ€ 원칙 SOLID μ•Œμ•„λ³΄μž

μœ μ‚¬κ°œλ°œμžΒ·2024λ…„ 6μ›” 5일
0

이둠곡뢀

λͺ©λ‘ 보기
9/10
post-thumbnail

πŸ“Œ κ°œμš”

SOLIDλŠ” 객체 지ν–₯ μ„€κ³„μ—μ„œ μ€‘μš”ν•œ 5가지 원칙을 μ˜λ―Έν•œλ‹€. 이 원칙은 클린 μ•„ν‚€ν…μ²˜μ˜ μ €μž λ‘œλ²„νŠΈ λ§ˆν‹΄μ΄ μ œμ•ˆν–ˆμœΌλ©°, μ†Œν”„νŠΈμ›¨μ–΄ κ°œλ°œμ—μ„œ μ˜μ‘΄μ„±μ„ κ΄€λ¦¬ν•˜κ³ , μœ μ§€λ³΄μˆ˜μ™€ ν™•μž₯성을 ν–₯μƒμ‹œν‚€κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€. 개인적으둜 SOLID 원칙을 κ³΅λΆ€ν•˜κΈ° μ „κ³Ό ν›„λ‘œ μ½”λ“œλ³΄λŠ” μ‹œμ„ μ΄ 많이 λ‹¬λΌμ‘Œλ‹€κ³  λŠλ‚€λ‹€.

πŸ“– 단일 μ±…μž„ 원칙(Single Responsibility Principle, SRP)

ν•˜λ‚˜μ˜ ν΄λž˜μŠ€λŠ” ν•˜λ‚˜μ˜ μ±…μž„λ§Œ κ°€μ Έμ•Ό ν•œλ‹€λŠ” 원칙이닀. 클래슀 뿐만 μ•„λ‹ˆλΌ λ©”μ†Œλ“œμ—λ„ ν•΄λ‹Ήν•˜λŠ” 것 κ°™λ‹€.
그런데 μ±…μž„? μ±…μž„μ΄ 뭘까 μ±…μž„μ΄λž€ 역할이닀 ν•˜λ‚˜μ˜ 클래슀, ν•˜λ‚˜μ˜ λ©”μ†Œλ“œλ§ˆλ‹€ 각각의 μ—­ν• κ³Ό 관심사λ₯Ό 응집λ ₯있게 λ§Œλ“€μ–΄ 이λ₯Ό 톡해 ν΄λž˜μŠ€κ°€ λ”μš± λͺ…확해지고, μž¬μ‚¬μš©μ— μš©μ΄ν•˜κ²Œ λ§Œλ“€λΌλŠ” μ˜λ―Έμ΄λ‹€.

이λ₯Ό μ™œ μ§€μΌœμ•Ό ν• κΉŒ? κ·Έλƒ₯ ν•œκ³³μ—λ‹€ λ‹€ λͺ°μ•„μ„œ ν•˜λ©΄ μ•ˆλ˜λ‚˜? 각각의 μ—­ν• λ³„λ‘œ ν΄λž˜μŠ€μ™€ λ©”μ†Œλ“œλ₯Ό λ§Œλ“€λ©΄ 괜히 μ½”λ“œμ–‘λ§Œ λŠ˜μ–΄λ‚˜μ§€ μ•Šλ‚˜? 그렇지 μ•Šλ‹€ μ•„λž˜ μ˜ˆμ‹œλ₯Ό 보자.

public class μ‚¬λžŒ {

    void μΌν•˜κΈ°() {
        System.out.println("μ½”λ“œλ₯Ό 타닀λ‹₯ 타닀λ‹₯ μΉ˜λŠ”μ€‘ ...");
    }
    
    void νš¨λ„ν•˜κΈ°() {
        System.out.println("λΆ€λͺ¨λ‹˜κ»˜ νš¨λ„ν•˜λŠ”μ€‘ ...");
    }
    
    void λ°μ΄νŠΈν•˜κΈ°() {
        System.out.println("μ—¬μžμΉœκ΅¬λž‘ 데이트 ν•˜λŠ”μ€‘ ...");
    }
    
}

μ‚¬λžŒμ΄λΌλŠ” ν΄λž˜μŠ€λŠ” μ½”λ”©ν•˜κΈ°, νš¨λ„ν•˜κΈ°, λ°μ΄νŠΈν•˜κΈ° λΌλŠ” μ•‘μ…˜μ„ ν•  수 μžˆλŠ” λ©”μ†Œλ“œλ“€μ΄ μžˆλ‹€.

λ§Œμ•½ νšŒμ‚¬, μ—„λ§ˆ, μ—¬μžμΉœκ΅¬ λΌλŠ” ν΄λž˜μŠ€λ“€μ΄ μžˆλ‹€κ³  κ°€μ •ν•  λ•Œ 이 ν΄λž˜μŠ€λ“€μ€ μœ μ‚¬ 개발자 클래슀λ₯Ό μ˜μ‘΄ν•˜κ²Œ 될 것 이닀. μ—¬κΈ°μ„œ νšŒμ‚¬ ν΄λž˜μŠ€μ—μ„œλŠ” νš¨λ„ν•˜κΈ°μ™€ λ°μ΄νŠΈν•˜κΈ°μ™€ 같은 κΈ°λŠ₯은 ν•„μš”λ„ μ—†λŠ”λ° μ‚¬μš©ν•  수 있게 λœλ‹€.

벌써 맛이 μ—†λ‹€.

또, μ‚¬λžŒ 클래슀λ₯Ό μˆ˜μ •ν•  λ•Œ λ§ˆλ‹€ ν•΄λ‹Ή ν΄λž˜μŠ€λ“€μ΄ 영ν–₯을 λ°›κ²Œ λœλ‹€.

이λ₯Ό κ°œμ„ ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ—­ν• λ³„λ‘œ 클래슀λ₯Ό λ§Œλ“€κ³  λ©”μ†Œλ“œλ₯Ό λΆ„λ¦¬ν•œλ‹€λ©΄ SRPλ₯Ό μ‹€μ²œν•  수 μžˆκ²Œλœλ‹€.

πŸ“– 개방-νŽ˜μ‡„ 원칙(Open/Closed Principle, OCP)

클래슀, λͺ¨λ“ˆ, ν•¨μˆ˜ 등은 ν™•μž₯μ—λŠ” μ—΄λ €μžˆμ§€λ§Œ λ³€κ²½μ—λŠ” λ‹«ν˜€μžˆμ–΄μ•Ό ν•œλ‹€λŠ” 원칙이닀. 즉, κΈ°μ‘΄ μ½”λ“œλ₯Ό λ³€κ²½ν•˜μ§€ μ•Šκ³ λ„ κΈ°λŠ₯을 μˆ˜μ •ν•˜κ±°λ‚˜ ν™•μž₯ν•  수 μžˆλŠ” ꡬ쑰λ₯Ό μ˜λ―Έν•œλ‹€.

그런데 μ–΄λ–»κ²Œ κΈ°μ‘΄ μ½”λ“œλ₯Ό 변경을 μ•ˆν•˜κ³  ν™•μž₯ν•  수 μžˆλŠ”κ±ΈκΉŒ? μ•„λž˜ μ˜ˆμ‹œλ₯Ό 보자

public class UserRepository {
	private final MysqlConnector connector;
    
    public UserRepository(mysqlConnector connector) {
    	this.connector = connector;
    }
    
    public void save(User user) {
    	connector.save(user);
    }
}

λ³„λ‘œ μ΄μƒν•œ 점이 μ—†λŠ” μ½”λ“œμ§€λ§Œ, λ§Œμ•½ DBMSλ₯Ό MySQLμ—μ„œ Oracle둜 λ³€κ²½ν•΄μ•Ό ν•œλ‹€λ©΄ μ–΄λ–¨κΉŒ?

UserRepositoryκ°€ μ˜μ‘΄ν•˜κ³  μžˆλŠ” MysqlConnector뢀뢄을 OracleConnectorλ₯Ό λ§Œλ“€μ–΄μ„œ μˆ˜μ •ν•΄μ•Ό ν•  것이닀.
그런데 λ§Œμ•½ μ‹œμŠ€ν…œ μ „μ²΄μ—μ„œ MysqlConnectorλ₯Ό μ˜μ‘΄ν•˜κ³  μžˆλŠ” μ½”λ“œλ₯Ό λ³€κ²½ν•΄μ•Ό ν•œλ‹€ μƒκ°ν•˜λ©΄ μ–΄μ§ˆμ–΄μ§ˆ ν•˜λ‹€.

이λ₯Ό κ°œμ„ ν•˜κΈ° μœ„ν•΄μ„  μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ‚¬μš©ν•˜μ—¬ DatabaseConnector λΌλŠ” 좔상적인 κ°œλ…μ„ λ§Œλ“€λ©΄ λœλ‹€.

핡심은 UserRepositoryκ°€ νŠΉμ • dbλ²€λ”μ˜ μ˜μ‘΄ν•˜μ§€ μ•Šκ²Œλ” ν•˜λŠ”κ²ƒμ΄λ‹€. κ°œμ„ λœ μ½”λ“œλ₯Ό 보자

// DB연결뢀와 save λ©”μ†Œλ“œλ₯Ό 좔상화 μ‹œν‚¨ ν›„ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ κ΅¬ν˜„μ²΄λ“€
public interface DatabaseConnector {
    void save(User user);
}

public class MysqlConnector implements DatabaseConnector {
    @Override
    public void save(User user) {
        System.out.println("Saving user to MySQL database");
    }
}

public class OracleConnector implements DatabaseConnector {
    @Override
    public void save(User user) {
        System.out.println("Saving user to Oracle database");
    }
}
// κ΅¬ν˜„μ²΄μ— μ˜μ‘΄ν•˜μ§€ μ•Šκ³  μΈν„°νŽ˜μ΄μŠ€μ— μ˜μ‘΄ν•¨μœΌλ‘œμ¨ OCPλ₯Ό 지킨 μ½”λ“œ
public class UserRepository {
    private final DatabaseConnector connector;
    
    public UserRepository(DatabaseConnector connector) {
        this.connector = connector;
    }
    
    public void saveUser(User user) {
        connector.save(user);
    }
}

이제 Mysql이던 Oracle이던 Maria던 상관없이 DatabaseConnector만 κ΅¬ν˜„ν•œ κ΅¬ν˜„μ²΄λ₯Ό κ½‚μ•„μ£ΌκΈ°λ§Œ ν•˜λ©΄ UserRepositoryλ₯Ό μˆ˜μ •ν•˜μ§€ μ•Šκ³ λ„ κΈ°λŠ₯을 ν™•μž₯ν•˜κ±°λ‚˜ μˆ˜μ •ν•  수 μžˆκ²Œλ˜μ—ˆλ‹€.

πŸ“– λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙(Liskov Substitution Principle, LSP)

λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙은 1988λ…„ 바바라 λ¦¬μŠ€μ½”ν”„κ°€ μ˜¬λ°”λ₯Έ 상속 κ΄€κ³„μ˜ νŠΉμ§•μ„ μ •μ˜ν•˜κΈ° μœ„ν•΄ λ°œν‘œν•œ 것이닀.
μ„œλΈŒ νƒ€μž…μ€ μ–Έμ œλ‚˜ 기반 νƒ€μž…μœΌλ‘œ ꡐ체할 수 μžˆμ–΄μ•Ό ν•œλ‹€λŠ” 것을 λœ»ν•œλ‹€.

ꡐ체할 수 μžˆλ‹€λŠ” 것은 μžμ‹ ν΄λž˜μŠ€λŠ” μ΅œμ†Œν•œ μžμ‹ μ˜ λΆ€λͺ¨ ν΄λž˜μŠ€μ—μ„œ κ°€λŠ₯ν•œ ν–‰μœ„λŠ” μˆ˜ν–‰μ΄ 보μž₯λ˜μ–΄μ•Ό ν•œλ‹€λŠ” μ˜λ―Έμ΄λ‹€.

즉, λΆ€λͺ¨ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•˜λŠ” μœ„μΉ˜μ— μžμ‹ 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό λŒ€μ‹  μ‚¬μš©ν–ˆμ„ λ•Œ μ½”λ“œκ°€ μ›λž˜ μ˜λ„λŒ€λ‘œ μž‘λ™ν•΄μ•Ό ν•œλ‹€λŠ” μ˜λ―Έμ΄λ‹€.

μ΄λŸ¬ν•œ LSP 원칙을 잘 μ μš©ν•œ μ˜ˆμ‹œκ°€ μžλ°”μ˜ μ»¬λ ‰μ…˜ ν”„λ ˆμž„μ›Œν¬ 이닀.

만일 λ³€μˆ˜μ— LinkedList μžλ£Œν˜•μ— λ‹΄μ•„ μ‚¬μš©ν•˜λ‹€, 쀑간에 μ „ν˜€ λ‹€λ₯Έ HashSet μžλ£Œν˜•μœΌλ‘œ 바꿔도 add() λ©”μ„œλ“œ λ™μž‘μ„ 보μž₯λ°›κΈ° μœ„ν•΄μ„œλŠ” Collection μ΄λΌλŠ” μΈν„°νŽ˜μ΄μŠ€ νƒ€μž…μœΌλ‘œ λ³€μˆ˜λ₯Ό μ„ μ–Έν•˜μ—¬ ν• λ‹Ήν•˜λ©΄ λœλ‹€.

μ™œλƒν•˜λ©΄ μΈν„°νŽ˜μ΄μŠ€ Collection의 좔상 λ©”μ„œλ“œλ₯Ό 각기 ν•˜μœ„ μžλ£Œν˜• ν΄λž˜μŠ€μ—μ„œ implementsν•˜μ—¬ μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„ κ·œμ•½μ„ 잘 지킀도둝 잘 섀계가 λ˜μ–΄μžˆκΈ° λ•Œλ¬Έμ΄λ‹€.

void main() {
	// Collection μΈν„°νŽ˜μ΄μŠ€ νƒ€μž…μœΌλ‘œ λ³€μˆ˜ μ„ μ–Έ
    Collection data = new LinkedList();
    data = new HashSet(); // 쀑간에 μ „ν˜€ λ‹€λ₯Έ μžλ£Œν˜• 클래슀λ₯Ό 할당해도 ν˜Έν™˜λ¨
    
    modify(data); // λ©”μ†Œλ“œ μ‹€ν–‰
}

void modify(Collection data){
    data.add(1); // μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„ ꡬ쑰가 잘 μž‘ν˜€μžˆκΈ° λ•Œλ¬Έμ— add λ©”μ†Œλ“œ λ™μž‘μ΄ 각기 μžλ£Œν˜•μ— 맞게 보μž₯됨
}

λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙을 μ–΄κΈ΄ μ‰¬μš΄ μ˜ˆμ‹œλ‘œλŠ” λ‹€μŒκ³Ό κ°™λ‹€.

// λΆ€λͺ¨ 클래슀
class μƒˆ {
    public void λ‚ λ‹€() {
        System.out.println("μƒˆκ°€ λ‚ κ³  μžˆλ‹€.");
    }
}

// μžμ‹ 클래슀
class μ°Έμƒˆ extends μƒˆ {
    @Override
    public void λ‚ λ‹€() {
        System.out.println("μ°Έμƒˆκ°€ λ‚ κ³  μžˆλ‹€.");
    }
}

// μžμ‹ 클래슀
class 타쑰 extends μƒˆ {
   
    @Override
    public void λ‚ λ‹€() {
        throw new UnsupportedOperationException("νƒ€μ‘°λŠ” λ‚  수 μ—†λ‹€.");
    }
}

μƒˆλΌλŠ” λΆ€λͺ¨ ν΄λž˜μŠ€μ—λŠ” λ‚ λ‹€λΌλŠ” λ©”μ†Œλ“œκ°€ μžˆλŠ”λ° νƒ€μ‘°μ˜ 경우 μƒˆμ˜ ν•œ μ’…λ₯˜ 이긴 ν•˜μ§€λ§Œ λ‚  μˆ˜λŠ” μ—†λ‹€.
이 처럼 μƒμ†κ΄€κ³„μ—μ„œμ˜ λͺ¨ν˜Έν•œ 관계가 λ°œμƒν•œ 경우 이λ₯Ό 적절히 ν•΄κ²°ν•΄μ€˜μ•Ό ν•œλ‹€.
ν•΄λ²•μœΌλ‘œλŠ” λ‚ λ‹€λΌλŠ” κΈ°λŠ₯을 μΈν„°νŽ˜μ΄μŠ€λ‘œ λ§Œλ“€μ–΄ 적절히 κ΅¬ν˜„ν•˜κ²Œ λ§Œλ“€μ–΄μ£Όλ©΄ λœλ‹€.

// λΆ€λͺ¨ 클래슀
class μƒˆ {
    // κ³΅ν†΅λœ 속성 및 λ©”μ„œλ“œ μ •μ˜
}

interface λ‚ μˆ˜μžˆλŠ” {
    void fly();
}

// μžμ‹ 클래슀
class μ°Έμƒˆ extends μƒˆ implements λ‚ μˆ˜μžˆλŠ” {
    @Override
    public void λ‚ λ‹€() {
        System.out.println("μ°Έμƒˆκ°€ λ‚ κ³  μžˆλ‹€.");
    }
}

// μžμ‹ 클래슀
class 타쑰 extends μƒˆ {
    
}

πŸ“– μΈν„°νŽ˜μ΄μŠ€ 뢄리 원칙(Interface Segregation Principle, ISP)

μΈν„°νŽ˜μ΄μŠ€ 뢄리 원칙은 νŠΉμ • ν΄λΌμ΄μ–ΈνŠΈλ₯Ό μœ„ν•œ μΈν„°νŽ˜μ΄μŠ€ μ—¬λŸ¬ κ°œκ°€ λ²”μš© μΈν„°νŽ˜μ΄μŠ€ ν•˜λ‚˜λ³΄λ‹€ λ‚«λ‹€λŠ” 원칙이닀.
κ·Έ μ΄μœ λŠ” λ²”μš© μΈν„°νŽ˜μ΄μŠ€μ˜ 경우 κ΅¬ν˜„ν•΄μ•Όν•  좔상 λ©”μ„œλ“œλ“€μ΄ 많이 있기 λ•Œλ¬Έμ— λ‚΄κ°€ κ΅¬ν˜„ν•˜κ³  싢지 μ•Šμ•„λ„ κ΅¬ν˜„ν•΄μ•Όλ§Œ ν•˜λŠ” λΆˆμƒμ‚¬κ°€ μΌμ–΄λ‚˜κΈ° λ•Œλ¬Έμ΄λ‹€.

interface μž‘μ—…μž {
    void μž‘μ—…ν•˜λ‹€();
    void μ‹μ‚¬ν•˜λ‹€();
}

class μ‚¬λžŒμž‘μ—…μž implements μž‘μ—…μž {
    public void μž‘μ—…ν•˜λ‹€() {
        System.out.println("μ‚¬λžŒμ΄ μž‘μ—…ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€");
    }
    
    public void μ‹μ‚¬ν•˜λ‹€() {
        System.out.println("μ‚¬λžŒμ΄ μ‹μ‚¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€");
    }
}

class λ‘œλ΄‡μž‘μ—…μž implements μž‘μ—…μž {
    public void μž‘μ—…ν•˜λ‹€() {
        System.out.println("λ‘œλ΄‡μ΄ μž‘μ—…ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€");
    }
    
    // λ‘œλ΄‡μ€ 먹을 ν•„μš”κ°€ μ—†μ§€λ§Œ, μΈν„°νŽ˜μ΄μŠ€ λ•Œλ¬Έμ— μ‹μ‚¬ν•˜λ‹€() λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€
    public void μ‹μ‚¬ν•˜λ‹€() {
        throw new UnsupportedOperationException("λ‘œλ΄‡μ€ 식사할 수 μ—†μŠ΅λ‹ˆλ‹€");
    }
}

πŸ“– μ˜μ‘΄μ„± μ—­μ „ 원칙(Dependency Inversion Principle, DIP)

μ˜μ‘΄μ„± μ—­μ „ μ›μΉ™μ΄λž€ κ³ μˆ˜μ€€ κ°œλ…μ€ μ €μˆ˜μ€€ κ°œλ…μ„ μ˜μ‘΄ν•΄μ„œλŠ” μ•ˆλœλ‹€λŠ” 원칙이닀. 그리고 μΆ”μƒν™”λŠ” ꡬ체적인 사항에 μ˜μ‘΄ν•΄μ„œλŠ” μ•ˆλ˜λ©°, ꡬ체적인 사항이 좔상화에 μ˜μ‘΄ν•΄μ•Ό ν•œλ‹€λŠ” μ˜λ―Έμ΄λ‹€.

그럼 μ—¬κΈ°μ„œ κ³ μˆ˜μ€€μ€ 뭐고 μ €μˆ˜μ€€μ€ λ¬΄μ—‡μΌκΉŒ?

κ³ μˆ˜μ€€ : μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 정책을 μ •μ˜ν•˜λŠ” κ°œλ…μ΄λ‹€. 주둜 λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ ν¬ν•¨ν•œλ‹€.
μ €μˆ˜μ€€ : κ³ μˆ˜μ€€ κ°œλ…μ„ μ§€μ›ν•˜λŠ” μ˜μ—­μœΌλ‘œ ꡬ체적은 κ΅¬ν˜„ μ„ΈλΆ€ 사항을 ν¬ν•¨ν•œλ‹€. 데이터 베이슀 μ ‘κ·Ό, λ„€νŠΈμ›Œν¬ 톡신 λ“±μ˜ 세뢀적인 κΈ°λŠ₯을 λ‹΄λ‹Ήν•œλ‹€.

interface DataRepository {
    void save(Object data);
}

class FileDataRepository implements DataRepository {
    public void save(Object data) {
        // 파일 μ‹œμŠ€ν…œμ— 데이터 μ €μž₯
    }
}

class HighLevelModule {
    private DataRepository dataRepository;

    public HighLevelModule(DataRepository dataRepository) {
        this.dataRepository = dataRepository;
    }

    public void performAction(Object data) {
        dataRepository.save(data);
    }
}
profile
κ°œλ°œμžμ™€ μœ μ‚¬ν•œ κ°œλ°œμžμž…λ‹ˆλ‹€

0개의 λŒ“κΈ€