이펙티브 자바 4장 : 클래스의 인터페이스

32192 단어 JavaJava

15. 클래스의 맴버의 접근 권한을 최소화해라

어슬프게 설계된 컴포넌트와 잘 설계된 컴포넌트의 가장 큰 차이는 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로 부터 얼마나 잘 숨겼느냐이다.

자바에서 위와 같은 정보 은닉 을 위한 다양한 장치를 제공하는데 그 중 하나가 접근 제한자 이다


1) public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다. public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않다.

2) 클래스에서 public static final 배열 필드를 두거나 이필드를 반환하는 접근자 메서드를 제공해서는 안된다.

// 보안 적으로 안좋다.
public static final Thing[] Values = {..} 


같은 경우 클라이언트에서 그 배열의 내용을 수정할 수 있다

해결 방법은 다음과 같다.

2-1) private 로 만들고 public 불변 리스트를 추가


private static final Thing[] PRIVATE_VALUES = { .. };

public static final List<Thing> VALUES = 
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

2-2) private 로 만들고 그 복사본을 public 으로 반환하는 메서드 만듦


private static final Thing[] PRIVATE_VALUES = { .. };

public static final Thing[] VALUES() { 
	return RIVATE_VALUES.clone();
    
}


16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

= 변수를 private 로 사용하고, getter, setter 등을 사용하라



17. 변경 가능성을 최소화하라

클래스를 불변으로 만드는 5가지 규칙

1) 객체의 상태를 변경하는 메서드(변경자) 를 제공하지 않는다.
2) 클래스를 확장할 수 없게 한다. 즉, 상속을 막는다. 대표적인 것은 final 사용하기
3) 모든 필드를 final 로 선언한다. 상속 불가
4) 모든 필드를 private 로 선언한다. 외부 접근 불가
5) 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없게한다. 변경할수 있는 컴포넌트가 있으면 객체 참조 불가능하게 함.


불변 객체(final)의 장점

1) 불변 객체는 단순하다. 생성될때부터 삭제될때 까지 그대로를 간직
2) 불변 객체는 근본적으로 스레드 안전하여 따로 동기화 할 필요가 없다. 그래서 안심하고 공유가능하다.
3) 불변 객체는 자유롭게 공유할 수 있음은 물론, 불변객체끼리는 내부 데이터를 공유할수 있다.
4) 객체를 만들 때 다른 불변 객체들은 구성요소로 사용하면 이점이 많다.
5) 불변 객체는 그 자체로 실패 원자성을 제공한다.

  • 실패 원자성이란?
    : 메서드에 예외가 발생한 뒤에도 그 객체는 여전히 메서드 호출 전과 같이 유효한 상태

6) 클래스는 꼭 필요한 경우가 아니면 불변이어야 하는데 모든 클래스를 불변으로 만들 수 없다면 변경할 수 있는 부분이라도 최소한으로 줄여야 한다.


public calss Complex {
	private final double re;
    private final double im;
    
    private Complex (double re, double im {
    	this.re =re;
        this.im = im; 
    }
    
    public static Complex valueOf(double re, double i) {
    	return new Complex(re,im);
    }
	
}


18. 상속 보다는 컴포지션을 사용해라

상속은 재사용성을 늘리는 강력한 수단이지만, 메서드 호출과 달리 상속은 캡슐화를 깨트린다. 상위 클래스에 따라 하위클래스에 문제가 발생할 수 있다.

이런 상황을 방지하기 위해서는 컴포지션을 사용하면 된다. 다만들어 놓고 조립해라

컴포지션이란?
: 기존 클래스를 사용할수 있는 새로운 클래스를 만들고 기존 클래스의 메서드를 사용할 수 있게 한다. 이미 다 구현 해두고 내가 원하는 것만 뽑아 쓴다.

public class ForwardingSet<E> implements Set<E> {
    
    private final Set<E> s;
    
   public ForwardingSet(Set<E> s) { this.s = s; }

    public void clear()               { s.clear();            }
    public boolean contains(Object o) { return s.contains(o); }
    public boolean isEmpty()          { return s.isEmpty();   }
    public int size()                 { return s.size();      }
    public Iterator<E> iterator()     { return s.iterator();  }
    public boolean add(E e)           { return s.add(e);      }
    public boolean remove(Object o)   { return s.remove(o);   }
    public boolean containsAll(Collection<?> c)
                                   { return s.containsAll(c); }
    public boolean addAll(Collection<? extends E> c)
                                   { return s.addAll(c);      }
    public boolean removeAll(Collection<?> c)
                                   { return s.removeAll(c);   }
    public boolean retainAll(Collection<?> c)
                                   { return s.retainAll(c);   }
    public Object[] toArray()          { return s.toArray();  }
    public <T> T[] toArray(T[] a)      { return s.toArray(a); }
    @Override public boolean equals(Object o)
                                       { return s.equals(o);  }
    @Override public int hashCode()    { return s.hashCode(); }
    @Override public String toString() { return s.toString(); }
    
    
    
    
}

public class InstrumentedSet<E> extends ForwardingSet<E> {
    private int addCount = 0;

    public InstrumentedSet(Set<E> s) {
        super(s);
    }

    @Override public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
    public int getAddCount() {
        return addCount;
    }
}


19. 상속을 고려해 설계하고 문서화하라, 그러지 않았다면 상속을 금지해라

1) 상속용 클래스는 재정의 할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.
2) 상속용으로 설계한 클래스는 배포전에 반드시 하위 클래스를 만들어 검증하고 이 클래스가 상속으로 사용 가능 한지 확인해아한다.
3) 상속용 클래스의 생성자는 직접적으로든 간접적으로는 재정의 가능 메서드를 호출해서는 안된다.

앞서 언급했듯이 상속은 재사용성을 늘리는 대신 캡슐화를 깨트린다. 다른 사람이 상속의 기능을 믿고 구현했지만 재대로 동작하지 않고 객체를 깨트릴 수 있다. 주의가 필요하다.



20. 추상 클래스보다는 인터페이스를 우선시해라

인터페이스는 믹스인 정의에 안성 맞춤이다. 믹스인이란 클래스가 구현할 수 있는 타입으로 , 믹스인을 구현한 클래스에 원래의 주된 타입 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 준다.

인터페이스로 계층 구조가 없는 타입 프레임워크를 만들수 있다.


public interface Singer {
   AudioClip sing(Song s);
}

public interface Songwriter {
    Song compose(int chartPosition);
}


public interface SingerSongwriter {
    
    abstract AudioClip strum();
    abstract void actSensitive();
}

인터페이스는 기능을 향상 시키는 안전하고 강력한 수단이 된다.



21. 인터페이스는 구현하는 쪽을 생각해서 설계하라

추상클래스에는 디폴트메서드를 사용한다. 자바 8 이상 부터의 인터페이스에도 마찬가지다 하지만 생각할 수 있는 모든 상황에서 불변식을 해치지 않느 디폴드 메서드를 작성하기는 어렵다.

디폴트 메서드는 컴파일에 성공하더라도 기존 구현체에 런타임 오류를 일으킬 수 있다. 그렇기 때문에 인터페이스 설계할 대는 여전히 세심한 주의를 기울려야한다.



22. 인터페이스는 타입을 정의하는 용도로만 사용해라

인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입을 말한다. 달리말해, 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 사용할 수 있는지 클라이언트에게 이야기 해주는 것이다. 인터페이스는 꼭 이용도로만 사용해야한다. 상수 공개용으로 사용하면 안된다.

사용 예시

  public class Test {
  	double atoms(double mols) {
  	return AVOCADROS_NUM * mol;
    }
  }
  


23. 태그 달린 클래스 보다는 클랫 계층 구조를 활용하라

태그가 달린 클래스 : 두가지 이상의 의미를 표현할 수 있으며, 구중 현재를 표현하는 의미를 태그로 알려주는 클래스. 1은 비오고 2는 햇빛 3은 눈 이런 것이 태그

태그가 달린 클래스에는 단점이 한 가득. 열거 타입 선언 태그 필드 스위치문. 이런 것은 장황하고 오류를 내기 쉽고 비효율 적이다.

안좋은 예시


public class Figure {
	 final Shape s;
} 

계층 구조로 변경하자.


abstract class Figure {
  abstract double area();
}

class Circle extends Figure {
  final double radius;

  Circle(double radius) { this.radius = radius; }

  @Override double area() { return Math.PI * (radius * radius); }
}

class Rectangle extends Figure {
  final double length;
  final double width;

  Rectangle(double length, double width) {
      this.length = length;
      this.width  = width;
  }
  @Override double area() { return length * width; }
}


24. 멤버 클래스는 되도록 static 으로 만들라

중첩 클래스는 다른 클래스 안에 정의된 클래스를 말하는데 중첩클래스의 종류는 1) 정적 맴버 클래스, 맴버 클래스, 익명 클래스, 지역 클래스가 있다. 이중 첫번째를 제외하면 내부 클래스에 해당된다.

여기서 정적 맴버 클래스는 다른 클래스 안에 선언된고 바깥 클래스의 private 맴버에서 접근할 수 있다는 점만 제외하면 일반 클래스와 같다.

정적 맴버클래스와 비정적 맴버 클래스는 static 만 차이난 것 처럼 보이지만, 맴버 클래스의 인스턴스가 각각 바깥 인스턴스를 사용한다면 비정적으로, 아니면 바깥 인스턴스에 접근할 일이 없다면 static 을 붙여서 정적 맴버 클래스로 만들자.

이유는 참조가 생기면 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못하는 가비지 컬렉션이 발생할 수 있다. 즉 메모리 누수 생길 수 있다.



## 25. 톱레벨 클래스는 한 파일 에 하나만 담자

톱 레벨 클래스란? 가장 상위의 클래스

소스 파일 하나에 톱레벨 클래스를 여러 개 선언하더라도 자바 컴파일러는 불평하지 않는다. 소스 파일 하나는 반드시 톱레벨 클래스를 하나만 담자.

   # Utensil.java
     class Utensil {
    	static final String NAME = "pan";
    }
 

아니면 static 을 이용해 분리하는 것도 있다.

public class Test {

  public static void main(String[] args) {
      System.out.println(Utensil.NAME + Dessert.NAME);
  }
  
  private static class Utensil {
  	static final String NAME = "pan";
  }

  private static class Dessert {
      static final String NAME = "cake";
  }
}

좋은 웹페이지 즐겨찾기