본문 바로가기

객체지향설계 5원칙(S.O.L.I.D)

O. 개방-폐쇄 원칙(Open-Closed Principle)

개방-폐쇄원칙 이란?

개방 폐쇄 원칙은 확장에는 열려 있고, 수정에 대해서는 닫혀있어야 한다는 원칙이다. 다시 말하면 객체의 확장에는 열려 있고, 객체의 수정은 닫혀있다라고 말할 수 있다.

개방-폐쇄원칙이 지켜지지 않은 코드의 문제점

public class UserDao {
    private static final String MYSQL_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:13306/tobi?useSSL=false";
    private static final String DB_USER = "root";
    private static final String DB_PASS = "masterpw";

    public int add(User user) throws ClassNotFoundException, SQLException {
        Class.forName(MYSQL_DRIVER);
        String query = "insert into users(id, name, password) values (?,?,?)";
        Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
        PreparedStatement ps = conn.prepareStatement(query);

        try (conn; ps) {
            ps.setLong(1, user.getId());
            ps.setString(2, user.getName());
            ps.setString(3, user.getPassword());

            return ps.executeUpdate();
        }
    }
}

public class User {
    private Long id;
    private String name;
    private String password;

    protected User() {
    }

    private User(Long id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public static User of(Long id, String name,  String password) {
        return new User(id, name, password);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;   
        }
        if (!(o instanceof User)) {
            return false;   
        }
        User user = (User) o;
        return Objects.equals(id, user.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }
}
  • 해당 어플리케이션을 제공해야할 곳이 여러 곳이라면 개발자는 UserDao를 제공할 곳에 맞게 커넥션 연결에 대한 정보를 수정해야 한다. 위와 같은 코드는 확장하기 좋지 않은 코드이다. 상속을 통해 코드를 확장할 수 있다.

예제 리팩토링1. 상속

public abstract class UserDao {
    public abstract Connection getConnection() throws ClassNotFoundException, SQLException;

    public int add(User user) throws ClassNotFoundException, SQLException {
        String query = "insert into users(id, name, password) values (?,?,?)";
        Connection conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(query);

        try (conn; ps) {
            ps.setLong(1, user.getId());
            ps.setString(2, user.getName());
            ps.setString(3, user.getPassword());

            return ps.executeUpdate();
        }
    }
}

public class AUserDao extends UserDao {
    private static final String MYSQL_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:13306/tobi?useSSL=false";
    private static final String DB_USER = "auser";
    private static final String DB_PASS = "apass";

    @Override
    public Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName(MYSQL_DRIVER);
        return DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
    }
}

public class BUserDao extends UserDao {
    private static final String MYSQL_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:13306/tobi?useSSL=false";
    private static final String DB_USER = "buser";
    private static final String DB_PASS = "bpass";

    @Override
    public Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName(MYSQL_DRIVER);
        return DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
    }
}
  • 공통 기능은 슈퍼 클래스의 concrete method 형태로 작성하고 서로 다른 커넥션 연결 방식에 대한 문제는 abstract method로 해결할 수 있다. 그러나 자바는 다중 상속을 지원하지 않기 때문의 위와 같은 리팩토링은 커넥션 연결에 대한 상속 이외의 용도로 UserDao를 사용할 수가 없게 된다. 또한, 연결 정보가 변경되었을 시 하위 클래스를 변경해야 하는 단점도 있다. 위와 같은 문제점은 인터페이스를 통해 줄일 수 있다.

예제 리팩토링2. 인터페이스를 이용한 의존성 분리

public interface ConnectionMaker {
    Connection makeConnection() throws ClassNotFoundException, SQLException;
}

public class AConnectionMaker implements ConnectionMaker {
    private static final String MYSQL_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:13306/tobi?useSSL=false";
    private static final String DB_USER = "auser";
    private static final String DB_PASS = "apass";

    @Override
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
        Class.forName(MYSQL_DRIVER);
        return DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
    }
}

public class BConnectionMaker implements ConnectionMaker {
    private static final String MYSQL_DRIVER = "com.mysql.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://localhost:13306/tobi?useSSL=false";
    private static final String DB_USER = "buser";
    private static final String DB_PASS = "bpass";

    @Override
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
        Class.forName(MYSQL_DRIVER);
        return DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
    }
}

public class UserDao {
    private final ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }

    public int add(User user) throws ClassNotFoundException, SQLException {
        Connection conn = connectionMaker.makeConnection();
        String query = "insert into users(id, name, password) values (?,?,?)";
        PreparedStatement ps = conn.prepareStatement(query);

        try (conn; ps) {
            ps.setLong(1, user.getId());
            ps.setString(2, user.getName());
            ps.setString(3, user.getPassword());

            return ps.executeUpdate();
        }
    }
}
  • 기존의 코드에서 슈퍼 클래스와 하위 클래스간의 의존성 문제를 인터페이스를 통해 해결하였다. connection 연결에 대한 부분을 클라이언트에게 제공하게 되면 연결에 대한 정보가 변경될 때에도 애플리케이션에 영향을 주지 않는다. 연결에 대한 비즈니스와 사용자 기능에 대한 비즈니스를 분리시켰기 때문에 개발자는 사용자 비즈니스가 변경될 경우 변경된 애플리케이션을 제공하면 된다. 위와 같이 작성된 코드가 개방 폐쇄 원칙을 지키는 코드라고 할 수 있다.

https://github.com/haedoang/solid.git