불변 클래스가 필요한 이유는 무엇입니까?
불변 클래스가 필요한 시나리오를 얻을 수 없습니다.
그러한 요구 사항에 직면 한 적이 있습니까? 아니면 우리가이 패턴을 사용해야하는 실제 예를 들어 주시겠습니까?
다른 답변은 불변성이 좋은 이유를 설명하는 데 초점을 맞춘 것 같습니다. 그것은 매우 좋으며 가능할 때마다 사용합니다. 그러나 그것은 귀하의 질문이 아닙니다 . 나는 당신이 필요한 답과 예제를 얻고 있는지 확인하기 위해 당신의 질문을 한 점씩 가져갈 것입니다.
불변 클래스가 필요한 시나리오를 얻을 수 없습니다.
여기서 "Need"는 상대적인 용어입니다. 불변 클래스는 모든 패러다임 / 패턴 / 도구와 마찬가지로 소프트웨어를 더 쉽게 구성 할 수있는 디자인 패턴입니다. 비슷하게, OO 패러다임이 나오기 전에 많은 코드가 작성되었지만, OO 를 "필요로 하는 " 프로그래머 중에는 저를 포함 시키십시오 . OO와 같은 불변 클래스는 반드시 필요한 것은 아니지만 필요한 것처럼 행동 할 것입니다.
그러한 요구 사항에 직면 한 적이 있습니까?
올바른 관점으로 문제 도메인의 개체를 보지 않으면 불변 개체에 대한 요구 사항이 표시되지 않을 수 있습니다 . 언제 유용하게 사용할지 잘 모르는 경우 문제 도메인에는 불변 클래스가 필요 하지 않다고 생각하기 쉽습니다 .
나는 종종 내 문제 영역의 주어진 객체를 값 또는 고정 인스턴스 로 생각하는 불변 클래스를 사용 합니다 . 이 개념은 때때로 관점이나 관점에 의존하지만 이상적으로는 좋은 후보 개체를 식별하기 위해 올바른 관점으로 쉽게 전환 할 수 있습니다.
불변 클래스에 대해 생각하는 방법에 대한 좋은 감각을 개발하기 위해 다양한 책 / 온라인 기사를 읽어 보면 불변 객체가 정말로 유용한 곳 (엄격히 필요한 것은 아니지만)을 더 잘 이해할 수 있습니다. 시작하기에 좋은 기사 중 하나는 Java 이론과 실습입니다. 돌연변이를 원하십니까?
나는 관점이 의미하는 바를 명확히하기 위해 어떻게 다른 관점 (변경 가능 vs 불변)에서 객체를 볼 수 있는지에 대한 몇 가지 예를 아래에 제공하려고합니다.
...이 패턴을 사용해야하는 실제 예를 들어 주시겠습니까?
실제 예제를 요청 했으므로 몇 가지를 제공하지만 먼저 몇 가지 고전적인 예제로 시작하겠습니다.
클래식 가치 객체
문자열과 정수이며 종종 값으로 간주됩니다. 따라서 String 클래스와 Integer 래퍼 클래스 (다른 래퍼 클래스뿐만 아니라)가 Java에서 변경 불가능하다는 사실은 놀라운 일이 아닙니다. 색상은 일반적으로 값으로 간주되므로 변경할 수없는 Color 클래스입니다.
반례
반대로 자동차는 일반적으로 가치 대상으로 간주되지 않습니다. 자동차를 모델링하는 것은 일반적으로 상태 (주행 거리계, 속도, 연료 수준 등)가 변경되는 클래스를 만드는 것을 의미합니다. 그러나 자동차가 가치 객체가 될 수있는 일부 도메인이 있습니다. 예를 들어 자동차 (또는 특히 자동차 모델)는 앱에서 특정 차량에 적합한 엔진 오일을 찾기위한 값 개체로 간주 될 수 있습니다.
카드 놀이
카드 놀이 프로그램을 작성한 적이 있습니까? 내가했다. 나는 트럼프 패를 수트와 계급이 변경 가능한 개체로 표현할 수 있었다. 드로 포커 핸드는 5 개의 고정 된 경우가 될 수 있습니다. 내 손에있는 5 번째 카드를 교체하는 것은 5 번째 카드를 새로운 카드로 바꾸는 것을 의미합니다.
그러나 나는 트럼프 패를 일단 만들어지면 변하지 않는 고정 된 수트와 랭크를 가진 불변의 개체로 생각하는 경향이있다. 제 드로우 포커 핸드는 5 개의 인스턴스가 될 것이고 제 핸드의 카드를 교체하는 것은 그 인스턴스 중 하나를 버리고 제 손에 새로운 무작위 인스턴스를 추가하는 것을 포함합니다.
지도 투영
마지막 예는지도가 다양한 투영으로 표시 될 수있는지도 코드를 작업했을 때 입니다. 원래 코드는지도에서 고정되었지만 변경 가능한 투영 인스턴스 (위의 변경 가능한 재생 카드처럼)를 사용했습니다. 지도 투영을 변경하면지도의 투영 인스턴스의 ivar (투영 유형, 중심점, 확대 / 축소 등)가 변경되었습니다.
그러나 투영을 불변의 값이나 고정 된 인스턴스로 생각하면 디자인이 더 간단하다고 느꼈습니다. 지도 투영을 변경한다는 것은지도의 고정 투영 인스턴스를 변경하는 대신지도가 다른 투영 인스턴스를 참조하도록하는 것을 의미했습니다. 이는 또한 MERCATOR_WORLD_VIEW
.
불변 클래스는 일반적으로 올바르게 설계, 구현 및 사용하기가 훨씬 간단합니다 . 예는 String입니다.의 구현은 대부분 불변성으로 인해 C ++ 의 구현 java.lang.String
보다 훨씬 간단 std::string
합니다.
불변성이 특히 큰 차이를 만드는 특정 영역 중 하나는 동시성입니다. 불변 객체는 여러 스레드간에 안전하게 공유 할 수있는 반면, 변경 가능한 객체는 신중한 설계 및 구현을 통해 스레드로부터 안전하게 만들어야합니다. 일반적으로 이것은 사소한 작업과 거리가 멀습니다.
업데이트 : Effective Java 2nd Edition 은이 문제를 자세히 다루고 있습니다. 항목 15 : 변경 가능성 최소화를 참조하십시오 .
다음 관련 게시물도 참조하십시오.
Joshua Bloch의 Effective Java는 불변 클래스를 작성하는 몇 가지 이유를 설명합니다.
- 단순성-각 클래스는 하나의 상태에만 있습니다.
- 스레드 안전-상태를 변경할 수 없기 때문에 동기화가 필요하지 않습니다.
- 변경 불가능한 스타일로 작성하면 더 강력한 코드가 생성 될 수 있습니다. 문자열이 불변이 아니라고 상상해보십시오. String을 반환 한 모든 getter 메서드는 String이 반환되기 전에 방어적인 복사본을 생성하도록 구현해야합니다. 그렇지 않으면 클라이언트가 실수로 또는 악의적으로 객체의 해당 상태를 손상시킬 수 있습니다.
일반적으로 심각한 성능 문제가 발생하지 않는 한 객체를 변경 불가능하게 만드는 것이 좋습니다. 이러한 상황에서 변경 가능한 빌더 객체는 StringBuilder와 같은 변경 불가능한 객체를 빌드하는 데 사용될 수 있습니다.
해시 맵은 전형적인 예입니다. 지도의 키는 변경 불가능해야합니다. 키가 변경 불가능하지 않고 hashCode ()가 새 값을 생성하도록 키의 값을 변경하면 이제 맵이 손상됩니다 (이제 키가 해시 테이블의 잘못된 위치에 있음).
Java는 사실상 하나의 모든 참조입니다. 때로는 인스턴스가 여러 번 참조됩니다. 이러한 인스턴스를 변경하면 모든 참조에 반영됩니다. 때로는 견고성과 스레드 안전성을 향상시키기 위해 이것을 원하지 않을 수도 있습니다. 그런 다음 변경 불가능한 클래스가 유용하므로 새 인스턴스 를 만들고 현재 참조에 다시 할당해야합니다. 이렇게하면 다른 참조의 원래 인스턴스가 그대로 유지됩니다.
Java String
가 변경 가능 하면 어떻게 보일지 상상해보십시오 .
우리는 그 자체로 불변 클래스 가 필요 하지 않지만 , 특히 여러 스레드가 관련된 경우 일부 프로그래밍 작업을 더 쉽게 만들 수 있습니다. 변경 불가능한 객체에 액세스하기 위해 잠금을 수행 할 필요가 없으며 이러한 객체에 대해 이미 설정 한 사실은 향후에도 계속 적용됩니다.
극단적 인 경우를 보자 : 정수 상수. "x = x + 1"과 같은 문을 작성하면 프로그램의 다른 곳에서 어떤 일이 발생하더라도 숫자 "1"이 어떻게 든 2가되지 않을 것이라고 100 % 확신하고 싶습니다.
이제 정수 상수는 클래스가 아니지만 개념은 동일합니다. 내가 다음과 같이 쓴다고 가정하자.
String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);
충분히 간단 해 보입니다. 그러나 Strings가 변경 불가능하지 않은 경우 getCustomerName이 customerId를 변경할 수있는 가능성을 고려해야하므로 getCustomerBalance를 호출 할 때 다른 고객에 대한 잔액을 얻습니다. 이제 "왜 누군가가 getCustomerName 함수를 작성하여 ID를 변경하게됩니까? 이건 말도 안됩니다."라고 말할 수 있습니다. 그러나 그것이 바로 당신이 곤경에 처할 수있는 곳입니다. 위의 코드를 작성하는 사람은 함수가 매개 변수를 변경하지 않는다는 것을 명백히 받아 들일 수 있습니다. 그런 다음 고객이 동일한 이름으로 여러 계정을 가지고있는 경우를 처리하기 위해 해당 기능의 다른 사용을 수정해야하는 누군가가 나타납니다. "이미 이름을 찾고있는이 편리한 getCustomer 이름 함수가 있습니다.
불변성은 단순히 특정 클래스의 객체가 상수임을 의미하며이를 상수로 취급 할 수 있습니다.
(물론 사용자는 변수에 다른 "상수 객체"를 할당 할 수 있습니다. 누군가는 String s = "hello"를 작성하고 나중에 s = "goodbye"를 작성할 수 있습니다. 변수를 최종화하지 않는 한 확신 할 수 없습니다. 내 자신의 코드 블록 내에서 변경되지 않습니다. 정수 상수처럼 "1"은 항상 같은 숫자이지만 "x = 1"은 "x = 2"를 써도 변경되지 않습니다. 변경 불가능한 객체에 대한 핸들이 있거나 전달하는 함수가 나에게 변경할 수 없거나 두 개의 사본을 만들면 하나의 사본을 보유하는 변수를 변경해도 변경되지 않는다고 확신 할 수 있습니다. 기타 등등.
불변성에는 여러 가지 이유가 있습니다.
- 스레드 안전성 : 변경 불가능한 객체는 변경되거나 내부 상태가 변경 될 수 없으므로 동기화 할 필요가 없습니다.
- 또한 내가 (네트워크를 통해) 보내는 모든 것이 이전에 보낸 것과 동일한 상태가되어야한다는 것을 보장합니다. 그것은 아무도 (도청 꾼)이 와서 내 불변 세트에 임의의 데이터를 추가 할 수 없음을 의미합니다.
- 개발하기도 더 간단합니다. 객체가 변경 불가능한 경우 하위 클래스가 존재하지 않음을 보장합니다. 예 :
String
수업.
따라서 네트워크 서비스를 통해 데이터를 보내고 싶을 때 전송 한 것과 똑같은 결과를 얻을 것이라는 보장 을 원하면 불변으로 설정하십시오.
나는 이것을 다른 관점에서 공격 할 것입니다. 불변의 객체는 코드를 읽을 때 삶을 더 쉽게 만듭니다.
변경 가능한 객체가 있으면 즉각적인 범위 밖에서 사용되는 경우 그 값이 무엇인지 결코 알 수 없습니다. MyMutableObject
메서드의 지역 변수를 만들고 값으로 채운 다음 5 개의 다른 메서드에 전달 한다고 가정 해 보겠습니다 . 이러한 메서드 중 하나는 내 개체의 상태를 변경할 수 있으므로 다음 두 가지 중 하나가 발생해야합니다.
- 내 코드의 논리에 대해 생각하면서 다섯 가지 추가 메서드의 본문을 추적해야합니다.
- 각 메서드에 올바른 값이 전달되도록하기 위해 5 개의 낭비되는 방어용 개체 복사본을 만들어야합니다.
첫 번째는 내 코드에 대한 추론을 어렵게 만듭니다. 두 번째는 내 코드의 성능을 저하시킵니다. 기본적으로 쓰기시 복사 (copy-on-write) 의미론을 사용하여 변경 불가능한 객체를 모방하고 있지만 호출 된 메서드가 실제로 내 객체의 상태를 수정하는지 여부에 관계없이 항상 수행합니다.
대신를 사용 MyImmutableObject
하면 내가 설정 한 값이 내 방법의 수명에 대한 값임을 확신 할 수 있습니다. 내 아래에서 그것을 바꿀 "원거리에서의 으스스한 행동"이 없으며 다섯 가지 다른 방법을 호출하기 전에 내 개체의 방어 복사본을 만들 필요가 없습니다. 다른 방법은 자신의 목적을 위해 변경 일을하려는 경우 그들은 그러나 그들은 정말 (내 각각의 모든 외부 메서드 호출하기 전에 그 일을 반대) 사본을해야 할 경우 그들은 단지 이렇게 - 복사를해야한다. 나는 현재의 소스 파일에도 없을 수있는 방법을 추적하는 정신적 자원을 아끼고, 만일을 대비하여 끊임없이 불필요한 방어 복사본을 만드는 오버 헤드를 시스템에 아끼지 않습니다.
(Java 세계를 벗어나 C ++ 세계로 들어가면 무엇보다도 더 까다로울 수 있습니다. 객체를 변경 가능한 것처럼 보이게 만들 수는 있지만 배후에서 투명하게 복제 할 수 있습니다. 어느 누구도 현명하지 않은 상태 변화 (기록 중 복사)입니다.)
향후 방문자를위한 2 센트 :
불변 객체가 좋은 선택이되는 두 가지 시나리오는 다음과 같습니다.
멀티 스레딩에서
다중 스레드 환경의 동시성 문제는 동기화를 통해 매우 잘 해결할 수 있지만 동기화는 비용이 많이 듭니다 ( "이유"에 대해서는 여기서 자세히 설명하지 않음). 따라서 변경 불가능한 객체를 사용하는 경우 동시성 문제를 해결하기위한 동기화가 없습니다. 변경 불가능한 객체는 변경할 수 없으며 상태를 변경할 수없는 경우 모든 스레드가 객체에 원활하게 액세스 할 수 있습니다. 따라서 불변 객체는 다중 스레드 환경의 공유 객체에 대한 훌륭한 선택입니다.
해시 기반 컬렉션의 핵심
해시 기반 컬렉션으로 작업 할 때 가장 중요한 점 중 하나는 키가 hashCode()
객체의 수명 동안 항상 동일한 값을 반환해야한다는 것입니다. 해당 값이 변경되면 해시 기반 컬렉션에 이전 항목이 만들어지기 때문입니다. 해당 개체를 사용하면 검색 할 수 없으므로 메모리 누수가 발생합니다. 불변 객체의 상태는 변경할 수 없으므로 해시 기반 수집의 키로 훌륭한 선택을합니다. 따라서 변경 불가능한 객체를 해시 기반 수집의 키로 사용하는 경우 그로 인해 메모리 누수가 발생하지 않는다는 것을 확신 할 수 있습니다 (물론 키로 사용 된 객체가 어디에서든 참조되지 않는 경우에도 메모리 누수가 발생할 수 있습니다. 그렇지 않으면 여기서 요점이 아닙니다).
final 키워드를 사용한다고해서 반드시 변경할 수있는 것은 아닙니다.
public class Scratchpad {
public static void main(String[] args) throws Exception {
SomeData sd = new SomeData("foo");
System.out.println(sd.data); //prints "foo"
voodoo(sd, "data", "bar");
System.out.println(sd.data); //prints "bar"
}
private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
Field f = SomeData.class.getDeclaredField("data");
f.setAccessible(true);
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.set(obj, "bar");
}
}
class SomeData {
final String data;
SomeData(String data) {
this.data = data;
}
}
프로그래머의 오류를 방지하기 위해 "final"키워드가 있다는 것을 보여주는 예제 일뿐입니다. 최종 키워드가없는 값을 재 할당하는 것은 우연히 쉽게 일어날 수있는 반면, 값을 변경하기 위해이 길이로 이동하는 것은 의도적으로 수행되어야합니다. 문서화 및 프로그래머 오류 방지를 위해 있습니다.
불변 데이터 구조는 재귀 알고리즘을 코딩 할 때도 도움이 될 수 있습니다. 예를 들어, 3SAT 문제 를 해결하려고한다고 가정 해보십시오 . 한 가지 방법은 다음을 수행하는 것입니다.
- 할당되지 않은 변수를 선택하십시오.
- TRUE 값을 지정하십시오. 이제 충족되는 절을 제거하여 인스턴스를 단순화하고 더 간단한 인스턴스를 해결하기 위해 반복하십시오.
- If the recursion on the TRUE case failed, then assign that variable FALSE instead. Simplify this new instance, and recur to solve it.
If you have a mutable structure to represent the problem, then when you simplify the instance in the TRUE branch, you'll either have to:
- Keep track of all changes you make, and undo them all once you realize the problem can't be solved. This has large overhead because your recursion can go pretty deep, and it's tricky to code.
- Make a copy of the instance, and then modify the copy. This will be slow because if your recursion is a few dozen levels deep, you'll have to make many many copies of the instance.
However if you code it in a clever way, you can have an immutable structure, where any operation returns an updated (but still immutable) version of the problem (similar to String.replace
- it doesn't replace the string, just gives you a new one). The naive way to implement this is to have the "immutable" structure just copy and make a new one on any modification, reducing it to the 2nd solution when having a mutable one, with all that overhead, but you can do it in a more efficient way.
One of the reasons for the "need" for immutable classes is the combination of passing everything by reference and having no support for read-only views of an object (i.e. C++'s const
).
Consider the simple case of a class having support for the observer pattern:
class Person {
public string getName() { ... }
public void registerForNameChange(NameChangedObserver o) { ... }
}
If string
were not immutable, it would be impossible for the Person
class to implement registerForNameChange()
correctly, because someone could write the following, effectively modifying the person's name without triggering any notification.
void foo(Person p) {
p.getName().prepend("Mr. ");
}
In C++, getName()
returning a const std::string&
has the effect of returning by reference and preventing access to mutators, meaning immutable classes are not necessary in that context.
They also give us a guarantee. The guarantee of immutability means that we can expand on them and create new patters for efficiency that are otherwise not possible.
http://en.wikipedia.org/wiki/Singleton_pattern
One feature of immutable classes which hasn't yet been called out: storing a reference to a deeply-immutable class object is an efficient means of storing all of the state contained therein. Suppose I have a mutable object which uses a deeply-immutable object to hold 50K worth of state information. Suppose, further, that I wish to on 25 occasions make a "copy" of my original (mutable) object (e.g. for an "undo" buffer); the state could change between copy operations, but usually doesn't. Making a "copy" of the mutable object would simply require copying a reference to its immutable state, so 20 copies would simply amount to 20 references. By contrast, if the state were held in 50K worth of mutable objects, each of the 25 copy operations would have to produce its own copy of 50K worth of data; holding all 25 copies would require holding over a meg worth of mostly-duplicated data. Even though the first copy operation would produce a copy of the data that will never change, and the other 24 operations could in theory simply refer back to that, in most implementations there would be no way for the second object asking for a copy of the information to know that an immutable copy already exists(*).
(*) One pattern that can sometimes be useful is for mutable objects to have two fields to hold their state--one in mutable form and one in immutable form. Objects can be copied as mutable or immutable, and would begin life with one or the other reference set. As soon as the object wants to change its state, it copies the immutable reference to the mutable one (if it hasn't been done already) and invalidates the immutable one. When the object is copied as immutable, if its immutable reference isn't set, an immutable copy will be created and the immutable reference pointed to that. This approach will require a few more copy operations than would a "full-fledged copy on write" (e.g. asking to copy an object which has been mutated since the last copy would require a copy operation, even if the original object is never again mutated) but it avoids the threading complexities that FFCOW would entail.
Immutable objects are instances whose states do not change once initiated. The use of such objects is requirement specific.
Immutable class is good for caching purpose and it is thread safe.
from Effective Java; An immutable class is simply a class whose instances cannot be modified. All of the information contained in each instance is provided when it is created and is fixed for the lifetime of the object. The Java platform libraries contain many immutable classes, including String, the boxed primitive classes, and BigInte- ger and BigDecimal. There are many good reasons for this: Immutable classes are easier to design, implement and use than mutable classes. They are less prone to error and are more secure.
Why Immutable class?
Once an object is instantiated it state cannot be changed in lifetime. Which also makes it thread safe.
Examples :
Obviously String, Integer and BigDecimal etc. Once these values are created cannot be changed in lifetime.
Use-case : Once Database connection object is created with its configuration values you might not need to change its state where you can use an immutable class
By the virtue of immutability you can be sure that the behavior/state of the underlying immutable object do not to change, with that you get added advantage of performing additional operations:
You can use multiple core/processing(concurrent/parallel processing) with ease(as the sequence of operations will no longer matter.)
Can do caching for expensive operations (as you are sure of the same
result).Can do debugging with ease(as the history of run will not be a concern
anymore)
참고URL : https://stackoverflow.com/questions/3769607/why-do-we-need-immutable-class
'Program Tip' 카테고리의 다른 글
PHP 및 MYSQL : JOIN 작업에서 모호한 열 이름을 해결하는 방법은 무엇입니까? (0) | 2020.10.25 |
---|---|
jQuery 이벤트에서 'this'의 값 제어 (0) | 2020.10.25 |
Java-int를 4 바이트의 바이트 배열로 변환 하시겠습니까? (0) | 2020.10.25 |
웹 앱의 '선택'에서 iPhone이 확대되지 않도록 방지 (0) | 2020.10.25 |
Facebook의 소셜 플러그인에 유동 폭을 설정할 수 있습니까? (0) | 2020.10.25 |