8주차 과제 : 인터페이스

Lee·2021년 1월 8일
0
post-thumbnail

자바에서 제공하는 인터페이스에 대해 학습해보자 🤔

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9

인터페이스 정의 💡

인터페이스란? 🤔

  • 클래스처럼 메소드와 변수가 있지만 실질적으로 메소드를 구현할 순 없고, 메소드를 선언만 할 수 있는 하나의 공간

인터페이스의 특징 🚀

  • 인터페이스는 이를 구현하는 구현체 클래스가 어떤 역할을 하는지, 어떤 기능들을 구현해야 하는지 지정해 준다.

  • 인터페이스는 게임 속 플레이어와 같이 능력에 관한 것들이다. 인터페이스를 구현하는 클래스는 반드시 인터페이스 안에 정의되어 있는 메소드를 구현해야 한다. 마치 우리가 게임 속 캐릭터를 방향 키를 누르면 캐릭터가 움직이는 것처럼 인터페이스에 정의되어 있는 메소드는 이를 구현하는 클래스에서 반드시 구현되어야 한다. 그래서 인터페이스는 클래스가 구현할 방법을 지정하는 역할을 한다.

  • 인터페이스를 구현할 때 모든 메소드를 구현하지 않으면 그 클래스는 추상 클래스로 만들어야 한다.

    추상 클래스에선 abstract키워드를 메소드에 붙힐 수 있어 상속받는 클래스에서 해당 부분만 구현하지만, 인터페이스에 정의된 메소드들은 구현체 클래스에서 필수로 구현하기 때문이다.

  abstract class AbsTest {
  
      public void method1() {
          System.out.println("implementation");
      }
  
      public abstract void method2(); // 상속받는 클래스에선 method2만 구현 해주면 된다.
  }
  
  class Test extends AbsTest{
  
      @Override
      public void method2() {
          System.out.println("method2 implementation");
      }
  }
  interface In1 {
  
      void method1();
      void method2();
  }
  
  class Test implements In1{ // abstract 클래스와는 달리 인터페이스에 정의된 메소드들은 구현체 클래스에서 다 구현해야한다.
  
      @Override
      public void method1() {
          System.out.println("method1() implementation");
      }
  
      @Override
      public void method2() {
          System.out.println("method2() implementation");
      }
  }

인터페이스의 문법 🚀

interface interface_name {
  
  // 멤버변수는 public static final 이여야 하며, 이를 생략할 수 있다.
  // 메소드는 public abstract로 선언되어야 하며, 이를 생략할 수 있다.
  // 기본적으로
}
  • 인터페이스를 구현하는 클래스는 인터페이스에서 선언된 모든 메소드들을 구현해야 한다.

왜 인터페이스를 사용할까? 🤔

  • 가장 큰 장점은 협업이다. 인터페이스를 구현하는 클래스들은 해당 인터페이스에 정의되어 있는 메소드들을 반드시 구현해야 하기 때문에 협업 시 다른 사람들과 코드의 내용은 달라도 동일한 기능을 동작하게끔 강제화 시킬 수 있기 때문이다.
  • 다형성으로 인해 이를 구현하는 하위 클래스에서 유지 보수하기 쉽다.

추상 클래스 vs 인터페이스 🤔

  • 추상 클래스는 말 그대로 확장, 상속을 의마함으로써 추상 메소드를 선언하여 상속을 통해 자식 클래스에서 완성을 유도하는 클래스이다. 그래서 미완성 설계도라고 표현한다.
  • 추상 클래스가 미완성 설계도라면 인터페이스는 기본 설계도이다.
  • 자바 8에서 추가된 기능들을 보기 전까진 그래도 추상 클래스와 인터페이스에 차이가 명확하다고 생각했지만.. 공부하고 난 이상.. 별차이를 모르겠다.. 🤔

인터페이스의 구현 🚀

interface Vehicle {

    void changeGear(int a);
    void speedUp(int a);
    void applyBrakes(int a);
}

class Bicycle implements Vehicle {

    int gear;
    int speed;

    @Override
    public void changeGear(int newGear) {
        gear = newGear;
    }

    @Override
    public void speedUp(int increment) {
        speed += increment;
    }

    @Override
    public void applyBrakes(int decrement) {
        speed -= decrement;
    }

    public void printStates() {
        System.out.println("speed : " + speed + " gear : " + gear);
    }
}

class Bike implements Vehicle {

    int speed;
    int gear;

    @Override
    public void changeGear(int newGear) {
        gear = newGear;
    }

    @Override
    public void speedUp(int increment) {
        speed += increment;
    }

    @Override
    public void applyBrakes(int decrement) {
        speed -= decrement;
    }

    public void printStates() {
        System.out.println("speed : " + speed + " gear : " + gear);
    }
}

public class GFG {

    public static void main(String[] args) {
      
        Bicycle bicycle = new Bicycle();
        bicycle.changeGear(2);
        bicycle.speedUp(3);
        bicycle.applyBrakes(1);

        System.out.println("Bicycle present state :");
        bicycle.printStates();

        Bike bike = new Bike();
        bike.changeGear(1);
        bike.speedUp(4);
        bike.applyBrakes(3);

        System.out.println("Bike present state :");
        bike.printStates();
    }
}

다형성 🚀

  • 현실 세계에서 한 사람이 동시에 다른 특성을 가질 수 있다. 예를 들어 한 남자는 집에서는 아버지이자 남편이고, 회사에선 회사원인 것처럼 상황에 따라서 다른 특성을 가지는 것을 다형성이라고 한다.
  • 다형성은 하나의 행동을 여러 방식으로 실행할 수 있게끔 해준다. 그렇기 때문에 다형성을 통해 하나의 인터페이스로 여러 개의 구현이 가능하다.
interface Man {

    void who();
}

class Father implements Man{
    @Override
    public void who() {
        System.out.println("나는 아빠");
    }
}

class Employee implements Man{

    @Override
    public void who() {
        System.out.println("나는 회사원");
    }
}

class PolymorphismTest {
    public static void main(String[] args) {

        Man father = new Father();
        father.who();

        Man employee = new Employee();
        employee.who();
    }
}

output
나는 아빠
나는 회사원

인터페이스를 구현하는 클래스들 중에서 어떤 클래스의 인스턴스가 who() 메서드를 실행시키는지에 따라서 실행되는 메서드가 달라진다.

  • Father클래스의 인스턴스로 attack()을 호출한다면, Father의 who()이 실행
  • Employee클래스의 인스턴스로 who()을 호출한다면 Employee의 who()이 실행

GameCharacter를 사용한 레퍼런스 참조 예시 (인터페이스 레퍼런스를 통해 구현체를 사용하는 방법-1) 🚀

  • 위에서 설명한 다형성으로 인해서 인터페이스 레퍼런스를 통해 구현체 클래스에서 다양한 결과를 얻을 수 있도록 해준다.
  • GameCharacter 인터페이스는 attack(), move() 메소드만 정의되어 있다. 이를 구현하는 Wizard, Warrior 클래스에서 각자 특성에 맞게 구현된 상태이다.
  • 일반적으로 우리가 몬스터를 잡는다고 했을때 각 캐릭터의 특징마다 움직이고 공격하는 방법이 다르다. 그렇기 때문에 Wizard, Warrior 객체를 생성한 후 attack(), move() 각각 호출할 것이다. 물론 이렇게 해도 문제가 되진 않는다. 하지만 코드의 중복이 존재한다. 우리가 예제로 공부할 때는 코드 라인 수가 적기 때문에 실질적으로 와닿진 않지만 만약 복잡한 프로그램을 고치고 있다고 가정하면서 공부를 한다면 조금 더 이해가 잘 될 것이다.
  • huntingMonster라는 메소드를 만들고 그 안에 attack(), move() 메소드를 호출시켜놓은 상태에서 타입만 GameCharacter 인터페이스 타입이 오게끔 정의해두면 이 인터페이스를 구현하고 있는 하위 구현체 클래스 타입에 구현되어 있는 attack(), move() 가 실행이 된다.
  • 일일이 Wizard, Warrior 객체를 생성하고 메소드를 호출하지 않아도 공통적인 기능들이지만 서로 다른 결과를 내야 할 때는 인터페이스 타입으로 다형성을 구현하는 방법이 좋은 방법이다.
interface GameCharacter {

    void attack();
    void move();
}

class Wizard implements GameCharacter {

    @Override
    public void attack() {
        System.out.println("마법봉으로 공격");
    }

    @Override
    public void move() {
        System.out.println("텔레포트를 이용하여 이동");
    }
    
}

class Warrior implements GameCharacter {

    @Override
    public void attack() {
        System.out.println("검을 이용한 공격");
    }

    @Override
    public void move() {
        System.out.println("점프를 이용하여 이동");
    }

}

public class Game {
    public static void main(String[] args) {

        Game game = new Game();
        game.huntingMonster(new Wizard());
    }

    public void huntingMonster(GameCharacter gameCharacter) {
        gameCharacter.move();
        gameCharacter.attack();
    }
}

Comparator를 사용한 레퍼런스 참조 예시 (인터페이스 레퍼런스를 통해 구현체를 사용하는 방법-2) 🚀

  • Comparator Interface는 우선 사용자가 정의한 클래스에 대해 정렬하는 메소드들을 제공하는 인터페이스이다.
  • Comparator을 통해 서로 다른 두 클래스의 객체를 비교할 수 있다.

Compare 메소드는 Comparator 인터페이스를 구현해야만 사용할 수 있는 메소드이다.

public int compare(T o1, T o2)

만약 우리가 지정한 타입의 클래스가 있다고 가정해보자,

import java.util.*;

class Wizard {
    int id;
    String nickname;
    String skill;

    public Wizard(int id, String nickname, String skill) {
        this.id = id;
        this.nickname = nickname;
        this.skill = skill;
    }

    @Override
    public String toString() {
        return "Wizard{" +
                "id=" + id +
                ", nickname='" + nickname + '\'' +
                ", skill='" + skill + '\'' +
                '}';
    }
}

class SortById implements Comparator<Wizard> {

    @Override
    public int compare(Wizard o1, Wizard o2) {
        return o1.id - o2.id;
    }
}

class SortByName implements Comparator<Wizard> {

    @Override
    public int compare(Wizard o1, Wizard o2) {
        return o1.nickname.compareTo(o2.nickname);
    }
}


public class Game {

    public static void main(String[] args) {
        ArrayList<Wizard> list = new ArrayList<Wizard>();
        list.add(new Wizard(333, "tedd", "fire"));
        list.add(new Wizard(222, "elsa", "frozen"));
        list.add(new Wizard(111, "anna", "water"));

        System.out.println("Unsorted");
        for (int i=0; i<list.size(); i++)
            System.out.println(list.get(i));

        Collections.sort(list, new SortById());

        System.out.println("\nSorted by rollno");
        for (int i=0; i<list.size(); i++)
            System.out.println(list.get(i));

        Collections.sort(list, new SortByName());

        System.out.println("\nSorted by name");
        for (int i=0; i<list.size(); i++)
            System.out.println(list.get(i));
    }

}

여기서 주목할 점은 SortById, SortByName 클래스에 Comparator 인터페이스를 구현하고 있다는 점과 Collection.sort를 이용하여 list에 있는 데이터를 정리하는 과정이다.

Collection.sort 의 내용을 분석해보면

public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }

첫 번째 매개변수로 List 타입이 들어오고, 두 번째 매개변수로는 Comparator 타입이 들어올 수 있다. 다시 Comparator 을 분석해보면 위에서 언급했지만 인터페이스로 선언되어 있다. 그 말은 우리가 이 Comprarator 인터페이스를 구현하는 구현체 클래스를 만들어주면 이 sort 메소드를 원하는 대로 사용할 수 있다는 뜻이다.

class SortById implements Comparator<Wizard> { // Comparator 인터페이스를 구현하여 compare 메소드를 재구현하는 과정이다.

    @Override
    public int compare(Wizard o1, Wizard o2) {
        return o1.id - o2.id;
    }
}

class SortByName implements Comparator<Wizard> {

    @Override
    public int compare(Wizard o1, Wizard o2) {
        return o1.nickname.compareTo(o2.nickname);
    }
}

자바의 다형성으로 인해서 Collection.sort()에 두 번째 매개변수로 우리가 지정한 SortById, SortByName 클래스 타입이 올 수 있는 것이다.

인터페이스의 상속 🚀

  • 클래스와 클래스 관계에선 단일 상속만 허용하지만, 인터페이스와 인터페이스 혹은 클래스와 인터페이스 사이에선 다중 상속이 가능하다.
class A {

}

class B extends A {
	// 단일 상속 가능
}

class C extends B, A {
  // 클래스와 클래스사이에선 다중상속 불가능
}
  • 이렇게 여러 개의 인터페이스를 정의하고 상속받을 수 있으며 상속받은 인터페이스를 클래스에 구현할 때에는 모든 메소드들을 구현해야 한다.
interface A {
  void methodA();
}

interface B {
  void methodB();
}

interface C extends B, A {
  void methodC();
}

public class MultipleInheritance implements C{ // A,B,C에 정의되어 있는 메소드들을 전부다 구현해야한다!
    @Override
    public void methodA() {
        System.out.println("A 인터페이스의 메소드입니다.");
    }

    @Override
    public void methodB() {
        System.out.println("B 인터페이스의 메소드입니다.");
    }

    @Override
    public void methodC() {
        System.out.println("C 인터페이스의 메소드입니다.");
    }
}

Default Method in Interface 🚀

자바 8버전 이전에는 인터페이스에서 메소드의 정의만 할 수 있었고, 이에 따른 구현은 할 수 없었다. 하지만 default 메소드를 이용하면 인터페이스에서 메소드를 구현할 수 있다. default 메소드는 특별한 경우에 사용되는데, 그렇다고 이러한 기능이 인터페이스를 구현한 클래스들에게 악영향을 미치진 않는다.

이미 잘 사용하고 있는 인터페이스에 새로운 기능을 추가한다고 생각해보자.. 이미 인터페이스를 구현하고 있는 클래스에서 새로운 메소드를 정의해야 한다는 내용에 에러를 뱉어낼 것이다.. 근데 구현체 클래스가 1 ~ 2개면 귀찮아도 별문제 없이 해결이 가능하지만.. 10개 아니 100개 된다면..?? 호..쒯…만약 해결하려면 최소한 10개 혹은 100개의 클래스 파일에 가서 재정의를 해야할 것이다.. 이를 방지하기 위해서 default 메소드가 나온 것이다.

default 메소드는 인터페이스 안에서 메소드를 정의하고 구현할 수 있으며 구현체 클래스에선 동일하게 메소드를 호출하면 아무런 문제 없이 사용할 수 있다.

Default Method 사용하기 이전 예 🚀

package com.lee.company;

// 위 다이어그램처럼 method4()를 추가한다고 가정해보자
// In1를 구현하고 있는 TestClass1, TestClass2, TestClass3 전부다 method4를 정의하여 사용해야한다.
// 현재 예시는 단순히 출력기능만 가지고 있지만 만약 100줄 혹은 1000줄 짜리 코드를 추가해야한다고 가정한다면?? 매우매우 끔찍할 것 같다..
interface In1 {
    void method1();
    void method2();
    void method3();
    void method4(); // 위 다이어그램처럼 메소드를 추가한다고 가정해보자 이를 구현하는 클래스에선 method4()를 구현해야하는 불상사가 발생한다.
}

class TestClass1 implements In1{

    @Override
    public void method1() {
        System.out.println("In1.method1()");
    }

    @Override
    public void method2() {
        System.out.println("In1.method2()");
    }

    @Override
    public void method3() {
        System.out.println("In1.method3()");
    }

}

class TestClass2 implements In1{

    @Override
    public void method1() {
        System.out.println("In1.method1()");
    }

    @Override
    public void method2() {
        System.out.println("In1.method2()");
    }

    @Override
    public void method3() {
        System.out.println("In1.method3()");
    }
}

class TestClass3 implements In1{

    @Override
    public void method1() {
        System.out.println("In1.method1()");
    }

    @Override
    public void method2() {
        System.out.println("In1.method2()");
    }

    @Override
    public void method3() {
        System.out.println("In1.method3()");
    }
}

class DefaultTest {
    public static void main(String[] args) {
        TestClass1 testClass1 = new TestClass1();
        testClass1.method1();
        testClass1.method2();
        testClass1.method3();

        TestClass2 testClass2 = new TestClass2();
        testClass2.method1();
        testClass2.method2();
        testClass2.method3();

        TestClass3 testClass3 = new TestClass3();
        testClass3.method1();
        testClass3.method2();
        testClass3.method3();

    }
}

Default Method 사용한 후 🚀

  • 위 다이어그램과 동일하게 In1 인터페이스에 method4를 추가할 경우이다.
package com.lee.company;

// default 메소드를 사용함으로써, In1를 구현하고 있는 구현체 클래스에 재정의할 필요없다.
// 인터페이스에서 바로 정의한 후 구현체 클래스에서 바로 사용 가능하다.
interface In1 {
    void method1();
    void method2();
    void method3();
    default void method4() {
      System.out.println("In1.method4()");
    }
}

class TestClass1 implements In1{

    @Override
    public void method1() {
        System.out.println("In1.method1()");
    }

    @Override
    public void method2() {
        System.out.println("In1.method2()");
    }

    @Override
    public void method3() {
        System.out.println("In1.method3()");
    }

}

class TestClass2 implements In1{

    @Override
    public void method1() {
        System.out.println("In1.method1()");
    }

    @Override
    public void method2() {
        System.out.println("In1.method2()");
    }

    @Override
    public void method3() {
        System.out.println("In1.method3()");
    }
}

class TestClass3 implements In1{

    @Override
    public void method1() {
        System.out.println("In1.method1()");
    }

    @Override
    public void method2() {
        System.out.println("In1.method2()");
    }

    @Override
    public void method3() {
        System.out.println("In1.method3()");
    }
}

class DefaultTest {
    public static void main(String[] args) {
        TestClass1 testClass1 = new TestClass1();
        testClass1.method1();
        testClass1.method2();
        testClass1.method3();
        testClass1.method4(); // 구현체 클래스타입의 변수로 접근하여 메소드를 호출할 수 있다.     

        TestClass2 testClass2 = new TestClass2();
        testClass2.method1();
        testClass2.method2();
        testClass2.method3();
        testClass2.method4();      

        TestClass3 testClass3 = new TestClass3();
        testClass3.method1();
        testClass3.method2();
        testClass3.method3();
        testClass3.method4();      

    }
}

Static Method in Interface 🚀

  • default 메소드와 같이 자바 8버전 이후에 나왔고, static 클래스 메소드에 접근할 때 객체 생성 없이 클래스 이름으로 메소드를 호출하는 것처럼 static 메소드를 가진 인터페이스를 구현하는 구현체 클래스에서 인터페이스명.메소드명 으로 접근하는 메소드이다
  • default 메소드와 동일하게 메소드의 구현체를 가지고 있고, 다른 점은 상속이 불가능하다는 점이다.
interface In1 {
    static void test() {
        System.out.println("static method");
    }
}

public class TestClass implements In1{
    public static void main(String[] args) {
        In1.test(); // 인터페이스명.메소드명으로 접근한다.
    }
}

Functional Interface 🚀

  • 메소드를 일급객체로 사용할 수 없는 자바 언어의 단점을 보완하기 위해 도입되었다.
  • 자바의 람다식은 함수형 인터페이스로만 접근이 가능하기 때문이다.
  • 일반적으로 Functional Interface는 (Object 클래스의 메소드를 제외하고) 구현해야 할 추상 메소드가 하나만 정의된 인터페이스 를 의미한다.
public interface FuntionalTest { // 구현해야할 메소드가 하나만 있기 때문에 Functional Interface에 속한다.
  public void test();
}

public interface FunctionalTest2 { // 구현해야할 메소드가 하나 이상이기 때문에 Functional Interface에 속하지 않는다.
  public void test2();
  public void test3();
}

public interface FunctionalTest3 { // Object 객체의 메소드만 인터페이스에 선언된 경우 Functional Interface에 속하지 않는다.
  public boolean equals(Object obj); 
}

public interface FunctionalTest4 { // Object 객체를 제외하고 하나의 추상 메소드가 존재하므로 Functional Interface에 속한다.
  public boolean equals(Object obj);
  public void execute();
}, FunctionalTest2, FunctionalTest3Functional Interface가 될 수 없고 나머지 두 개의 인터페이스는 Functional Interface가 될 수 있다.

Private Method in Interface 🚀

  • 인터페이스 안에서 코드의 재사용률을 높이기 위해 사용한다.
  • abstract로 선언할 수 없다.
  • 해당 인터페이스 안에서만 사용이 가능하다.
  • private static으로 시작하는 메소드는 다른 메소드가 non-static이든, static이든 접근 가능하다.
  • private 만 적용된 메소드는 static 메소드 안에선 사용할 수 없다.
public interface CustomInterface {
     
    public abstract void method1();
     
    public default void method2() {
        method4();  //private method inside default method
        method5();  //static method inside other non-static method
        System.out.println("default method");
    }
     
    public static void method3() {
        method5(); //static method inside other static method
        System.out.println("static method");
    }
     
    private void method4(){
        System.out.println("private method");
    } 
     
    private static void method5(){
        System.out.println("private static method");
    } 
}
 
public class CustomClass implements CustomInterface {
 
    @Override
    public void method1() {
        System.out.println("abstract method");
    }
     
    public static void main(String[] args){
        CustomInterface instance = new CustomClass();
        instance.method1();
        instance.method2();
        CustomInterface.method3();
    }
}

Output:
 
abstract method
private method
private static method
default method
private static method
static method
  

템플릿 메소드 패턴 💡

그렇다면 인터페이스에 이러한 기능들을 제공하는 이유가 뭘까? 위에서도 언급했지만 수정하기 쉽고, 재사용을 위한 기능들이라는 건 이해했다. 하지만 왜? 수정하기 쉽고, 재사용하기 쉽게 만들었냐는 것이다. 이번에도 역시 나의 개인적인 생각이지만, 코드의 중복을 제거하기 위함이라고 생각한다.

조금 더 찾아보니 템플릿 메소드 패턴(template method pattern)라는 것이 있었다. 소프트웨어 공학에서 동작 상의 알고리즘의 프로그램 뼈대를 정의하는 행위 디자인 패턴이다. 알고리즘의 구조를 변경하지 않고 알고리즘의 특정 단계들을 다시 정의할 수 있게 해준다.

템플릿(template)은 하나의 '틀'을 의미한다. 하나의 틀에서 만들어진 것들은 형태가 다 같다. 이런 틀 기능을 구현할 때는 template method 패턴을 이용할 수 있다. 이는 상속의 개념이 있는 상위 클래스와 하위 클래스의 구조에서 표현할 수 있다. 일반적으로 상위 클래스(현재 공부하고 있는 인터페이스)에는 추상 메서드를 통해 기능의 골격을 제공하고, 하위 클래스(구체 클래스)의 메서드에서는 세부 처리를 구체화한다.

이처럼 상위 클래스에서는 추상적으로 표현하고 그 구체적인 내용은 하위 클래스에서 결정되는 디자인 패턴을 template method 패턴이라고 한다. 상속의 개념처럼 template method 패턴도 코드 양을 줄이고 유지보수를 용이하게 만드는 역할을 한다. 따라서 유사한 서브 클래스가 존재할 때 template method 패턴을 사용하면 매우 유용하다.

예를 한번 들어보자. 음료를 만들기 위한 클래스가 하나 있으면 음료수를 만들기 위해서는 1)컵을 준비한다. 2)물을 붓는다 3)첨가물을 넣는다. 4)음료를 내어드린다. 이렇게 4가지의 음료 만드는 과정이 있다. 1,2,4번 과정은 모든 음료를 만드는 데 공통적인 과정이라면 3번은 어떤 음료인가에 따라 첨가물이 달라질 것이다. 예를 들면 커피면 커피 가루, 홍차면 홍차 가루를 넣을 것이다.

이렇게 변경되는 로직부분을 추상 메소드로 만들어 놓고 상위 클래스에서는 알고리즘의 전체적인 틀을 만들고 하위 클래스에서는 구체적인 알고리즘의 일부를 구현하는 것이다.

ps) static, default, private 메소드를 조사하다보니 템플릿 메소드 패턴이라는 것을 알게 되었고, 아직까진 완벽하게 나의 지식으로 습득하지 못했기에 정리가 잘 된 글을 퍼왔다.

/**
 * 공통 기능을 구현하고 세부 기능은 추상화한 추상클래스(음료제작)
 * @author yun-yeoseong
 * 
 */
public abstract class TemplateMethodPattern {
    
    public final void makeBeverage() {
        prepareCup();
        prepareWater();
        additive();
        finish();
    }
    
    /**
     * 공통 메소드
     */
    private void prepareCup() {
        System.out.println("컵을 준비한다.");
    }
    
    /**
     * 공통 메소드
     */
    private void prepareWater() {
        System.out.println("물을 붓는다.");
    }
    
    /**
     * 실제 구현이 필요한 부분
     */
    abstract void additive();
    
    /**
     * Hook 메소드, 서브클래스에서 구현이 필요하다면 오버라이드 해도된다.
     * 하지만 꼭 오버라이드가 강제는 아니다.
     */
    private void hookMethod() {
        System.out.println("hook method");
    }
    
    /**
     * 공통 메소드
     */
    private void finish() {
        System.out.println("음료를 내어드린다.");
    }
    
}
 
/**
 * 템플릿 추상 클래스를 상속하는 서브 클래스
 * 세부내용을 구현한다.
 * @author yun-yeoseong
 *
 */
public class SubClassA extends TemplateMethodPattern{
 
    @Override
    void additive() {
        System.out.println("커피가루를 넣는다.");
    }
    
    
}
 
/**
 * 템플릿 추상 클래스를 상속하는 서브 클래스
 * 세부내용을 구현한다.
 * @author yun-yeoseong
 *
 */
public class SubClassB extends TemplateMethodPattern{
 
    @Override
    void additive() {
        System.out.println("홍차가루를 넣는다.");
    }
    
    
}

일급객체? 🤔

  • 함수 자체가 파라미터로 전달할 수 있다.
  • 반환값으로 사용할 수 있다.
  • 변수나 데이터 구조 안에 담을 수 있다.
  • 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.
  • 지나친 의식의 흐름으로 어디까지 온 건 진 모르겠지만.. 함수형 인터페이스로 인해 메소드가 일급 객체로 사용될 수 있다는 것 같다.. 😞

람다식? 🤔

  • 익명 함수를 지칭하는 용어이다, 즉 식별자 없이 실행 가능한 함수를 람다식이라고 부른다.
  • 함수긴 함수지만 함수를 따로 정의하지 않고 코드 한 줄에 함수를 써서 안에 내용을 작성한 후 호출하는 방식이다.

람다식 사용 ✍️

  1. 람다식은 매개변수 -> 함수몸체의 형태로 이용할 수 있다.

  2. 매개변수가 하나일 경우 매개변수를 생략할 수 있다.

  3. 함수몸체가 단일 실행문이면 괄호{}를 생략 할 수 있다.

  4. 함수몸체가 return문으로만 구성되어 있는 경우 괄호{}를 생략 할 수 없다.

    1. (매개변수) -> {함수몸체}
    2. () -> {함수몸체}
    3. (매개변수) -> 함수몸체
    4. (매개변수) -> {return 0;}

람다식의 사용 예 📌

  • 함수형 인터페이스는 추상 메소드가 하나만 있는 인터페이스라고 위에서 공부했다. 이 함수형 인터페이스는 람다식에 할당 대상으로 사용될 수 있다.
  • Runnable Interface는 함수형 인터페이스로 이미 정의 되어있다. 그렇기 때문에 우리는 run 메소드를 람다식으로 표현할 수 있다.
  @FunctionalInterface
  public interface Runnable {
      /**
       * When an object implementing interface <code>Runnable</code> is used
       * to create a thread, starting the thread causes the object's
       * <code>run</code> method to be called in that separately executing
       * thread.
       * <p>
       * The general contract of the method <code>run</code> is that it may
       * take any action whatsoever.
       *
       * @see     java.lang.Thread#run()
       */
      public abstract void run();
  }
람다식을 사용하기 전
  public class TestClass {
      public static void main(String[] args) {
          new Thread(new Runnable() {
              @Override
              public void run() {
                  System.out.println("no Lamda");
              }
          }).start();
      }
  }
람다식을 사용한 후
  public class TestClass {
      public static void main(String[] args) {
          new Thread(()-> {
              System.out.println("Lamda");
          }).start();
      }
  
  }

출처 🧾

GeeksForGeeks

Private Interface

일급객체

람다

템플릿 메소드 패턴

0개의 댓글