[JAVA 객체지향프로그래밍] 인터페이스 (인터페이스를 사용하는 이유, 템플릿 메서드, DAO 구현)

    반응형

     

    이 글은 패스트 캠퍼스 한번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 강의를 듣고 공부한 내용을 바탕으로 정리한 글입니다.


     

    1. 인터페이스

    인터페이스란, 추상 메서드만을 가질 수 있고, 구현 메서드는 가질 수 없는 추상 클래스의 일종입니다. 인터페이스에서는 모든 메서드가 추상 메서드로 선언(public abstract)되고 모든 변수는 상수로 선언(public static final)됩니다. 기본적으로는 아래와 같이 인터페이스를 작성합니다.

    interface [interface name] {
    	float pi = 3.14F;
    	void someMethod();
    }

    일반적으로는 위과 같이 작성하고, 위의 예시에서 pi는 자동으로 public static final float로 상수로 선언되고, 메서드는 public abstract void로 선언이 됩니다.

     

    2. 인터페이스가 하는 일과 인터페이스를 사용하는 이유

    그러면 인터페이스는 하는 일도 없는 것 같은데, 왜 써야 할까요? 인터페이스는 동일한 목적 하에 동일한 기능을 수행하도록 강제하는 일종의 명세서라고 볼 수 있습니다. 즉, 클래스나 프로그램이 제공하는 기능을 명시적으로 선언해놓습니다.

    클라이언트는 서버가 어떻게 구현되어있는지 모두 알 필요가 없습니다. 인터페이스만 보고, '이게 구현되어있으니까 이렇게 써야겠구나'를 알고 가져다가 쓸 수 있는 것이지요.

    또, 인터페이스를 구현한 다양한 객체를 사용해서 다형성을 사용할 수 있습니다. 어떤 객체가 하나의 인터페이스 타입이라는 것은 그 인터페이스가 제공하는 모든 메서드를 구현했다는 의미입니다. 여러 클래스가 인터페이스 구현을 다양하게 구현할 수 있다는 것입니다.예를 들어, "붕어빵 만들기"라는 인터페이스가 있다면 각각 "팥 붕어빵 만들기", "슈크림 붕어빵 만들기" 클래스로 구현될 수 있다는 것이죠. 이것이 바로 다형성이고, 인터페이스를 사용하는 이유입니다.

    한 가지 중요한 예시를 더 들어보겠습니다. JDBC 인터페이스는 자바에서 DB 프로그래밍을 할 수 있게하는 인터페이스인데, 데이터베이스의 종류에 관계없이 사용할 수 있습니다. 그 이유는 JDBC는 인터페이스이고, 그 인터페이스가 DB랑 연결해서 해야하는 일들을 구현하는 것은 서드파티에서 합니다.(오라클, mysql이 jar 파일로 만들어놓습니다!)

    2. 인터페이스를 활용한 다형성 구현 (DAO 구현)

    자바 어플리케이션에서 DB에 따라 코드를 이중으로 관리하면 유지보수가 매우 힘듭니다. 따라서 인터페이스를 하나 정의하고, 그 인터페이스를 구현하는 클래스를 DB에 따라 여러개 만들어 사용해야 합니다. 이렇게 구현하면 다른 코드들은 모두 똑같이 유지하고, DAO만 달라집니다. 

    UserInfoDAO 인터페이스를 설계해보겠습니다. 이 인터페이스에서는 DB에 관계없이 user에 대한 DB operation을 할 때 구현해야 하는 기능을 모두 명시해놓기만하고, 실제 구현은 이 인터페이스를 implement하는 DB에 따른 클래스가 구현하게 될 것입니다.

    public interface UserInfoDAO {
    
        void insertUserInfo(UserInfo userInfo);
        void updateUserInfo(UserInfo userInfo);
        void deleteUserInfo(UserInfo userInfo);
    
    }

    간단히 insert, update, delete 기능만 넣겠습니다.

    그리고나서 UserInfoDAO를 각각 UserInfoMysqlDAOUserInfoOracleDAO로 구현해보겠습니다.

    public class UserInfoMysqlDAO implements UserInfoDAO {
    
        @Override
        public void insertUserInfo(UserInfo userInfo) {
            System.out.println("Insert into MYSQL DB user ID = " + userInfo.getUserId());
        }
    
        @Override
        public void updateUserInfo(UserInfo userInfo) {
            System.out.println("Update into MYSQL DB user ID = " + userInfo.getUserId());
        }
    
        @Override
        public void deleteUserInfo(UserInfo userInfo) {
            System.out.println("Delete from  MYSQL DB user ID = " + userInfo.getUserId());
        }
    }
    
    public class UserInfoOracleDAO implements UserInfoDAO {
    
        @Override
        public void insertUserInfo(UserInfo userInfo) {
            System.out.println("Insert into Oracle DB user ID = " + userInfo.getUserId());
        }
    
        @Override
        public void updateUserInfo(UserInfo userInfo) {
            System.out.println("Update  into Oracle DB user ID = " + userInfo.getUserId());
        }
    
        @Override
        public void deleteUserInfo(UserInfo userInfo) {
            System.out.println("Delete from Oracle DB user ID = " + userInfo.getUserId());
        }
    }
    

    간단히 print문으로만 구현하였습니다.

    UserInfo 클래스는 다음과 같이 작성합니다.

    public class UserInfo {
    
        private static int serialId = 0;
        private String userId;
        private String password;
        private String userName;
    
        public UserInfo() {
            this.userId = "user" + valueOf(serialId);
            this.userName = "no-name";
            serialId++;
        }
    
        public UserInfo(String userId, String password, String userName) {
            this.userId = userId;
            this.password = password;
            this.userName = userName;
        }
    
        public String getUserId() {
            return userId;
        }
    
        public void setUserId(String userId) {
            this.userId = userId;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    }
    

     

    그럼 이제, 메인함수를 작성해보겠습니다. 메인 함수에서는 db.properties라는 파일에서 DB가 MySQL인지, Oracle인지 확인 후, 그에 해당하는 UserInfoDAO를 생성한 후, DB operation(insert, update, delete)를 수행합니다.

    public class UserInfoClient {
    
        public static void main(String[] args) throws IOException {
            FileInputStream fis = new FileInputStream("db.properties");
    
            Properties prop = new Properties(); //pair로 읽어들이는 객체
            prop.load(fis);
            String dbType = prop.getProperty("DB-TYPE");
    
            UserInfo userInfo = new UserInfo();
            UserInfoDAO userInfoDao = null;
    
            if (dbType.equals("MYSQL")) {
                userInfoDao = new UserInfoMysqlDAO();
            }
            else if (dbType.equals("ORACLE")) {
                userInfoDao = new UserInfoOracleDAO();
            }
            else {
                System.out.println("DB Error");
                return ;
            }
    
            userInfoDao.insertUserInfo(userInfo);
            userInfoDao.updateUserInfo(userInfo);
            userInfoDao.deleteUserInfo(userInfo);
    
        }
    
    }

    db.properties 파일은 단순히 키와 밸류쌍으로 DB-TYPE=ORACLE으로 작성해주었습니다.

    • FileInputStreamdb.properties 파일을 읽어옵니다.
    • Properties는 데이터를 pair로 읽어들이는 객체로, db.properties의 내용을 key와 value pair로 읽어들입니다.
    • 읽어들인 DB 타입을 dbType 변수에 저장합니다.
    • dbType에 따라 해당하는 UserInfoDAO를 생성합니다.

    여기서 주목해야 하는 점은 만약 다른 종류의 DB가 추가되면, 해당 DB에 맞는 operation을 구현하는 클래스만 추가로 생성하기만 하면 끝이라는 것입니다! 그리고 인터페이스를 구현한 어떤 클래스를 사용했던, 인터페이스에 선언된 기능을 모두 사용하기만 하면 된다는 것이죠.(userInfoDaoUserInfoMySqlDAO의 객체이던 UserInfoOracleDAO의 객체이던 상관없이 insertUserInfo(), updateUserInfo(), deleteUserInfo()를 사용하면 됩니다.) 이것이 우리가 인터페이스를 사용하는 이유입니다. 

    위의 테스트 코드를 실행하면 다음과 같이 UserInfoOracleDAO가 생성되고 정상적으로 메서드가 호출되는 것을 확인할 수 있습니다.

     

    3. 인터페이스 형 변환

    인터페이스를 구현한 클래스는 인터페이스형으로 선언한 변수로 형 변환을 할 수 있습니다. 예를 들어, "붕어빵"을 구현한 "팥 붕어빵" 클래스는 "붕어빵"으로 형변환이 될 수 잇는 것이죠! 

    상속에서의 형변환과 동일합니다. 형 변환이 되는 경우, 인터페이스에 선언된 메서드만을 사용할 수 있습니다.

     

    4. 인터페이스의 여러가지 요소

    • default 메서드 (자바8 이후)

    default 메서드는 구현을 가지는 메서드입니다. 인터페이스 내의 메서드는 모든 메서드는 추상 메서드로, 구현부가 없는데, default 키워드가 붙은 메서드는 구현부를 가질 수 있고, 인터페이스를 구현하는 클래스에서 공통으로 사용할 수 있는 기본 메서드입니다. 물로, 구현하는 클래스에서 override하여 사용할 수 있습니다.

    default void desc() {
    	System.out.println("This is default method.")
    }
    • static 메서드 (자바8 이후)

    인스턴스 생성과 관계없이 인터페이스 타입으로 사용할 수 있는 메서드입니다. 인터페이스 내의 선언된 추상메서드나 default 메서드는 인터페이스가 구현되고, 그 구현된 클래스가 인스턴스를 생성했을 때 사용할 수 있습니다. 하지만, static 메서드는 모든 객체가 공유하는 메서드이므로 인스턴스가 생성되지 않아도 인터페이스 타입으로 바로 사용할 수 있는 메서드입니다.

    • private 메서드 (자바9 이후)

    private 메서드는 인터페이스 내부에서만 사용하기 위해 구현하는 메서드입니다. 구현부가 있지만, 인터페이스를 구현한 클래스에서 사용할 수 없고, 재정의할 수도 없습니다.

     

    5. 다중 구현

    자바에서 클래스는 다중 상속이 불가능하지만 인터페이스는 다중 구현이 가능합니다. 즉, 하나의 클래스가 여러 인터페이스를 implement할 수 있습니다.

    클래스가 다중 상속이 안되는 이유는 모호함을 없애기 위함이였습니다. 아래와 같이 diamond problem이 생기기 때문입니다. Child가 초록색 a()메서드를 상속받아야 할지, 파란색 a()메서드를 상속받아야할 지 모호합니다.

    하지만, 인터페이스는 구현부 자체가 없기 때문에 모호함이 생길일이 없습니다. implement하는 클래스가 무조건 구현부를 작성해야 하기 때문이지요.

     

    6. 인터페이스의 상속

    인터페이스 간에도 상속이 가능합니다. 동일하게 extends 키워드를 사용해서 상속할 수 있습니다. 인터페이스는 다중 상속이 가능하고, 구현 코드의 상속이 아니므로 타입 상속이라고 부릅니다.

    public interface X {
    	void x();
    }
    
    public interface Y {
    	void y();
    }
    
    public interface MyInterface extends X, Y {
    	void myMethod();
    }

    위의 예시에서는 MyInterfaceXY를 모두 상속하므로 MyInterfaceimplement하는 클래스가 x() 메서드와 y() 메서드를 모두 구현해야 합니다.

     

     

    반응형

    댓글