Mixin
이펙티브의 클래스와 인터페이스 부분을 읽다가 mixin이란 개념이 나왔는데
믹스인이란 클래스가 구현할 수 있는 타입으로 믹스인을 구현한 클래스에
원래의 '주된 타입' 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 준다.
예) Comparable은 자신을 구현한 클래스의 인스턴스들끼리는 순서를 정할 수 있다고 선언하는 믹스인 인터페이스이다.
이처럼 대상 타입의 주된 기능에 선택적 기능을 '혼합(mixed-in)'한다고 해서 믹스인이라고 부릅니다.
위 설명만으로 부족해 mixin에 대해 찾아봤는데
스택 오버플로에 나와 같은 고민으로 글을 작성한 사람도 있었다.
https://stackoverflow.com/questions/17987704/an-example-of-a-mixin-in-java
Using mixins in Java
클래스에 기능을 덧붙여야 할 때가 있는데 대부분 Mixin보다는 Delegator로 구현한다.
이번에는 일급 컬렉션(first class collections)을 사용하여 Java에서 Mixin을 구현하는 방법을 알아보자.
일급 컬렉션에 대해
https://woowacourse.github.io/javable/post/2020-05-08-First-Class-Collection/
https://jojoldu.tistory.com/412
exam1. 구독자 목록을 포함한 Magazine.java, Newspaper.java
일반적으로 다음과 같이 모델링한다.
// Magazine.java
class Magazine {
private List<Subcriber> subscribers;
public void addSub(Subscriber subscriber) {
subscribers.add(subscriber);
}
public List<Subcriber> primeSubscribers() {
. . .
}
}
// Newspaper.java
class Newspaper {
private DateTime date;
private List<Subcriber> subscribers;
public void addSub(Subscriber subscriber) {
subscribers.add(subscriber);
}
public List<Subcriber> primeSubscribers() {
. . .
}
}
구독자 목록 관련된 작업이 두 클래스에서 모두 중복된다.
구독자 목록인 List<Subcriber> subscribers
을
필드로 갖고 있는 다른 클래스를 만들면 이를 해결할 수 있다.
class Subcribers {
private List<Subcriber> subscribers;
}
List와 Subscribers을 별도의 클래스로 분리
custom arrayList에서 동작하게 하려면 List를 재정의해야 한다.
interface ListMixin<T> extends List<T> {
List<T> getRecords();
default int size() {
return getRecords().size();
}
. . .
}
Subscribers에서 ListMixin 인터페이스를 구현하고
getRecords()
메서드를 재정의하여 구독자 목록을 반환함으로써
이 클래스를 List처럼 사용할 수 있다.
구독자 리스트에 관련된 작업이 전부 이 클래스에 들어간다.
class Subcribers implements ListMixin<Subcriber> {
private List<Subcriber> subcribers = new ArrayList<>();
@Override
public List<Subcriber> getRecords() {
return subcribers;
}
public void addSub(Subscriber subscriber) {
subscribers.add(subscriber);
}
. . .
}
이제 Magazine.java
, Newspaper.java
에서
구독자 명단을 반환하는 로직을 다루지 않아도 된다.
class Magazine {
private Subcribers Subcribers;
. . .
}
exam2. mixin exam code
interface TimeStamped {
long getStamp();
}
class TimeStampedImp implements TimeStamped {
private final long timeStamp;
public TimeStampedImp() {
timeStamp = new Date().getTime();
}
public long getStamp() {
return timeStamp;
}
}
interface SerialNumbered {
long getSerialNumber();
}
class SerialNumberedImp implements SerialNumbered {
private static long counter = 1;
private final long serialNumber = counter++;
public long getSerialNumber() {
return serialNumber;
}
}
class Basic {
private String value;
public void set(String val) {
value = val;
}
public String get() {
return value;
}
}
class Mixin extends Basic implements TimeStamped, SerialNumbered {
private TimeStamped timeStamp = new TimeStampedImp();
private SerialNumbered serialNumber = new SerialNumberedImp();
public long getStamp() {
return timeStamp.getStamp();
}
public long getSerialNumber() {
return serialNumber.getSerialNumber();
}
}
public class Mixins {
public static void main(String[] args) {
Mixin mixin1 = new Mixin();
Mixin mixin2 = new Mixin();
mixin1.set("test string 1");
mixin2.set("test string 2");
System.out.println(mixin1.get() + " " + mixin1.getStamp() + " " + mixin1.getSerialNumber());
System.out.println(mixin2.get() + " " + mixin2.getStamp() + " " + mixin2.getSerialNumber());
}
}