불변 객체의 상태를 노출해도 괜찮습니까?
최근 불변 객체의 개념을 접하게 되었기 때문에 상태에 대한 액세스를 제어하는 모범 사례를 알고 싶습니다. 내 뇌의 객체 지향 부분이 공개 멤버의 눈에 두려움에 움츠리고 싶게 만들지 만 다음과 같은 기술적 인 문제는 보이지 않습니다.
public class Foo {
public final int x;
public final int y;
public Foo( int x, int y) {
this.x = x;
this.y = y;
}
}
필드를로 선언하고 private
각각에 대해 getter 메서드를 제공하는 것이 더 편하다고 느낄 수 있지만 상태가 명시 적으로 읽기 전용 일 때는 지나치게 복잡해 보입니다.
변경 불가능한 객체의 상태에 대한 액세스를 제공하는 가장 좋은 방법은 무엇입니까?
그것은 전적으로 객체를 어떻게 사용할 것인지에 달려 있습니다. 퍼블릭 필드는 본질적으로 악한 것이 아니라 모든 것을 퍼블릭으로 기본 설정하는 것이 나쁩니다. 예를 들어 java.awt.Point 클래스는 x 및 y 필드를 공용으로 만들고 최종적이지 않습니다. 귀하의 예제는 공개 필드를 잘 사용하는 것처럼 보이지만 다시 변경 불가능한 다른 객체의 모든 내부 필드를 노출하고 싶지 않을 수 있습니다. 포괄적 인 규칙은 없습니다.
나는 과거에도 똑같이 생각했지만 일반적으로 변수를 비공개로 만들고 getter와 setter를 사용하여 나중에 동일한 인터페이스를 유지하면서 구현을 변경할 수있는 옵션을 갖게됩니다.
이것은 내가 최근에 Robert C. Martin의 "Clean Code"에서 읽은 것을 생각 나게했습니다. 6 장에서 그는 약간 다른 관점을 제시합니다. 예를 들어, 95 페이지에서 그는 다음과 같이 말합니다.
"객체는 추상화 뒤에 데이터를 숨기고 해당 데이터에서 작동하는 기능을 노출합니다. 데이터 구조는 데이터를 노출하며 의미있는 기능이 없습니다."
그리고 100 페이지 :
콩의 유사 캡슐화는 일부 OO 순수 주의자들의 기분을 좋게 만드는 것처럼 보이지만 일반적으로 다른 이점은 제공하지 않습니다.
코드 샘플에 따르면 Foo 클래스는 데이터 구조처럼 보입니다. 따라서 Clean Code의 토론에서 이해 한 바에 따르면 (내가 준 두 인용문 이상) 클래스의 목적은 기능이 아닌 데이터를 노출하는 것이며, getter와 setter를 사용하는 것은 아마도 별 효과가 없을 것입니다.
다시 말하지만, 제 경험상 저는 일반적으로 getter 및 setter와 함께 개인 데이터의 "bean"접근 방식을 사용했습니다. 그러나 다시 한 번, 아무도 더 나은 코드를 작성하는 방법에 대한 책을 쓰라고 요청하지 않았으므로 Martin이 할 말이있을 수 있습니다.
객체가 로컬에서 충분히 사용되어 향후 API 변경을 중단하는 문제에 대해 신경 쓰지 않는 경우 인스턴스 변수 위에 게터를 추가 할 필요가 없습니다. 그러나 이것은 불변 객체에 국한되지 않는 일반적인 주제입니다.
게터를 사용의 장점은 간접적 인 추가 레이어에서 제공 할 수 있습니다 당신은 누구의 유틸리티 unforseeable 미래로 확장 할 것이다 널리 사용되는 오브젝트 등을 설계하는 경우 편리.
불변성에 관계없이 여전히이 클래스 의 구현 을 노출하고 있습니다. 어떤 단계에서 구현을 변경 (또는 Point 예제를 사용하여 다양한 파생을 생성하고 극좌표를 사용하는 유사한 Point 클래스를 원할 수 있음)을 원할 것이며 클라이언트 코드가 이에 노출됩니다.
위의 패턴은 유용 할 수 있지만 일반적으로 매우 지역화 된 인스턴스로 제한합니다 (예 : 정보의 튜플 전달 -겉보기에 관련되지 않은 정보의 객체는 잘못된 캡슐화이거나 정보 가 관련, 내 튜플은 완전한 객체로 변환됩니다)
명심해야 할 중요한 점은 함수 호출이 범용 인터페이스를 제공한다는 것입니다. 모든 개체는 함수 호출을 사용하여 다른 개체와 상호 작용할 수 있습니다. 올바른 서명을 정의하기 만하면됩니다. 유일한 문제는 이러한 함수 호출을 통해서만 상호 작용해야한다는 것입니다.이 함수는 종종 잘 작동하지만 어떤 경우에는 어색 할 수 있습니다.
상태 변수를 직접 노출하는 주된 이유는 이러한 필드에서 직접 기본 연산자를 사용할 수 있기 때문입니다. 잘 수행되면 가독성과 편의성을 향상시킬 수 있습니다. 예를 들어를 사용하여 복소수를 추가 +
하거나를 사용하여 키가있는 컬렉션에 액세스 할 수 []
있습니다. 구문을 사용하는 경우 기존 규칙을 따른다면이 방법의 이점은 놀랍습니다.
문제는 연산자가 범용 인터페이스가 아니라는 것입니다. 매우 특정한 내장 유형 집합 만 사용할 수 있으며 언어가 예상하는 방식으로 만 사용할 수 있으며 새로운 유형을 정의 할 수 없습니다. 그래서 일단 프리미티브를 사용하여 퍼블릭 인터페이스를 정의했다면, 그 프리미티브와 그 프리미티브 (그리고 쉽게 캐스팅 할 수있는 다른 것들) 만 사용하도록 자신을 고정 시켰습니다. 다른 것을 사용하려면 그 원시적 요소와 상호 작용할 때마다 춤을 추어 야합니다. 그러면 DRY 관점에서 죽을 수 있습니다. 모든 것이 매우 빨리 약해질 수 있습니다.
일부 언어는 연산자를 범용 인터페이스로 만들지 만 Java는 그렇지 않습니다. 이것은 Java의 기소가 아닙니다. 설계자는 의도적으로 연산자 오버로딩을 포함하지 않기로 선택했으며 그렇게 할 충분한 이유가있었습니다. 연산자의 전통적인 의미에 잘 맞는 것처럼 보이는 물체로 작업하는 경우에도 실제로 의미가있는 방식으로 작업하도록 만드는 것은 놀랍도록 미묘한 차이가있을 수 있습니다. 나중에 지불하십시오. 그 과정을 거치는 것보다 함수 기반 인터페이스를 읽기 쉽고 사용 가능하게 만드는 것이 종종 훨씬 쉬우 며, 연산자를 사용했을 때보 다 더 나은 결과를 얻을 수도 있습니다.
그러나 그 결정 에는 트레이드 오프 가 있었습니다 . 이 있는 운영자 기반 인터페이스는 정말보다 더 나은 작업을 수행 할 때 시간 함수 기반을하지만, 연산자 오버로딩하지 않고, 해당 옵션이 바로 사용할 수 없습니다. 어쨌든 작업자를 구둣 주려고 시도하면 실제로 결정하기를 원하지 않는 몇 가지 설계 결정에 갇히게 될 것입니다. 자바 설계자들은이 절충안이 가치가 있다고 생각했고, 그에 대해 옳았을 수도 있습니다. 그러나 이와 같은 결정은 낙진없이 오지 않습니다. 이런 상황이 낙진이 발생하는 곳입니다.
요컨대, 문제는 구현 자체를 노출하는 것이 아닙니다 . 문제는 그 구현에 자신을 고정시키는 것입니다.
실제로 어떤 방식 으로든 객체의 모든 속성을 노출하기 위해 캡슐화를 해제합니다. 모든 속성은 구현 세부 사항입니다. 모두가 이것을한다고해서 옳은 것은 아닙니다. 접근 자와 뮤 테이터 (게터와 세터)를 사용해도 더 나아지지는 않습니다. 오히려 캡슐화를 유지하기 위해 CQRS 패턴을 사용해야합니다.
당신이 개발 한 클래스는 현재의 화신으로 괜찮을 것입니다. 이 문제는 일반적으로 누군가이 클래스를 변경하거나 상속하려고 할 때 발생합니다.
예를 들어 위의 코드를보고 누군가 Bar 클래스의 다른 멤버 변수 인스턴스를 추가 할 생각을합니다.
public class Foo {
public final int x;
public final int y;
public final Bar z;
public Foo( int x, int y, Bar z) {
this.x = x;
this.y = y;
}
}
public class Bar {
public int age; //Oops this is not final, may be a mistake but still
public Bar(int age) {
this.age = age;
}
}
위 코드에서 Bar의 인스턴스는 변경할 수 없지만 외부 적으로 누구나 Bar.age의 값을 업데이트 할 수 있습니다.
The best practice is to mark all fields as private, have getters for the fields. If you are returning an object or collection, make sure to return unmodifiable version.
Immunatability is essential for concurrent programming.
I know only one prop to have getters for final properties. It is the case when you'd like to have access to the properties over an interface.
public interface Point {
int getX();
int getY();
}
public class Foo implements Point {...}
public class Foo2 implements Point {...}
Otherwise the public final fields are OK.
An object with public final fields that get loaded from public constructor parameters effectively portrays itself as being a simple data holder. While such data holders aren't particularly "OOP-ish", they are useful for allowing a single field, variable, parameter, or return value to encapsulate multiple values. If the purpose of a type is to serve as a simple means of gluing a few values together, such a data holder is often the best representation in a framework without real value types.
Consider the question of what you would like to have happen if some method Foo
wants to give a caller a Point3d
which encapsulates "X=5, Y=23, Z=57", and it happens to have a reference to a Point3d
where X=5, Y=23, and Z=57. If the thing Foo has is known to be a simple immutable data holder, then Foo should simply give the caller a reference to it. If, however, it might be something else (e.g. it might contain additional information beyond X, Y, and Z), then Foo should create a new simple data holder containing "X=5, Y=23, Z=57" and give the caller a reference to that.
Having Point3d
be sealed and expose its contents as public final fields will imply that methods like Foo may assume it's a simple immutable data holder and may safely share references to instances of it. If code exists that make such assumptions, it may be difficult or impossible to change Point3d
to be anything other than a simple immutable data holder without breaking such code. On the other hand, code which assumes Point3d
is a simple immutable data holder can be much simpler and more efficient than code which has to deal with the possibility of it being something else.
You see this style a lot in Scala, but there is a crucial difference between these languages: Scala follows the Uniform Access Principle, but Java doesn't. That means your design is fine as long as your class doesn't change, but it can break in several ways when you need to adapt your functionality:
- you need to extract an interface or super class (e.g. your class represents complex numbers, and you want to have a sibling class with polar coordinate representation, too)
- you need to inherit from your class, and information becomes redundant (e.g.
x
can be calculated from additional data of the sub-class) - you need to test for constraints (e.g.
x
must be non-negative for some reason)
Also note that you can't use this style for mutable members (like the infamous java.util.Date
). Only with getters you have a chance to make a defensive copy, or to change representation (e.g. storing the Date
information as long
)
I use a lot constructions very similar to the one you put in the question, sometimes there are things that can be better modeled with a (sometimes inmutable) data-strcuture than with a class.
All depends, if you are modeling an object, an object its defined by its behaviors, in this case never expose internal properties. Other times you are modeling a data-structure, and java has no special construct for data-structures, its fine to use a class and make public all the properties, and if you want immutability final and public off course.
For example, robert martin has one chapter about this in the great book Clean Code, a must read in my opinion.
In cases where the only purpose is to couple two values to each other under a meaningful name, you may even consider to skip defining any constructors and keep the elements changeable:
public class Sculpture {
public int weight = 0;
public int price = 0;
}
This has the advantage, to minimize the risk to confuse the parameter order when instantiating the class. The restricted changeability, if needed, can be achieved by taking the whole container under private
control.
Just want to reflect reflection:
Foo foo = new Foo(0, 1); // x=0, y=1
Field fieldX = Foo.class.getField("x");
fieldX.setAccessible(true);
fieldX.set(foo, 5);
System.out.println(foo.x); // 5!
So, is Foo
still immutable? :)
참고URL : https://stackoverflow.com/questions/21910769/is-it-okay-to-expose-the-state-of-an-immutable-object
'UFO ET IT' 카테고리의 다른 글
localStorage를 사용할 수 있는지 확인 (0) | 2020.11.23 |
---|---|
libz.so.1 : 공유 객체 파일을 열 수 없습니다. (0) | 2020.11.23 |
다중 처리를 사용하는 셀러리 병렬 분산 작업 (0) | 2020.11.23 |
Assert.AreEqual과 Assert.AreSame의 차이점 (0) | 2020.11.23 |
사용 사례 : InfluxDB 대 Prometheus (0) | 2020.11.23 |