code

긴 매개 변수 목록이있는 생성자를 사용하지 않고 크고 불변의 객체 만들기

codestyles 2020. 8. 25. 08:05
반응형

긴 매개 변수 목록이있는 생성자를 사용하지 않고 크고 불변의 객체 만들기


불변 할 수있는 큰 (3 개 이상의 필드) 개체가 있습니다. 이 경우가 발생할 때마다 긴 매개 변수 목록으로 생성자 혐오감을 만드는 경향이 있습니다.

옳지 않고 사용하기 어렵고 가독성이 떨어집니다.

필드가 목록과 같은 일종의 수집 유형이면 더 나쁩니다. 단순 addSibling(S s)하면 객체 생성이 너무 쉬워 지지만 객체를 변경 가능하게 렌더링합니다.

그런 경우에 너희들은 무엇을 사용합니까?

저는 Scala와 Java를 사용하고 있지만 언어가 객체 지향적이라면 문제는 언어 불가지론 적이라고 생각합니다.

내가 생각할 수있는 솔루션 :

  1. "긴 매개 변수 목록이있는 생성자 혐오"
  2. 빌더 패턴

글쎄, 일단 생성되면 읽기 쉽고 불변 객체를 모두 원하십니까?

유창한 인터페이스가 올바르게 완료 되면 도움이 될 것이라고 생각 합니다.

다음과 같이 보일 것입니다 (순수하게 구성된 예제).

final Foo immutable = FooFactory.create()
    .whereRangeConstraintsAre(100,300)
    .withColor(Color.BLUE)
    .withArea(234)
    .withInterspacing(12)
    .build();

나는 대부분의 자바 프로그래머가 유창한 인터페이스를 잘못 이해하고 객체를 빌드하는 데 필요한 방법으로 객체를 오염시키기 때문에 굵게 "CORRECTLY DONE" 이라고 썼습니다 . 물론 완전히 잘못된 것입니다.

트릭은 build () 메서드 만 실제로 Foo를 생성한다는 것입니다 (따라서 Foo는 변경 불가능할 수 있음).

FooFactory.create () , 여기서 XXX (..)withXXX (..) 모두 "다른 것"을 만듭니다.

다른 것이 FooFactory 일 수 있습니다. 여기에 한 가지 방법이 있습니다 ....

FooFactory는 다음과 같습니다.

// Notice the private FooFactory constructor
private FooFactory() {
}

public static FooFactory create() {
    return new FooFactory();
}

public FooFactory withColor( final Color col ) {
    this.color = color;
    return this;
}

public Foo build() {
    return new FooImpl( color, and, all, the, other, parameters, go, here );
}

Scala 2.8에서는 copy케이스 클래스 메서드 뿐만 아니라 명명 된 매개 변수와 기본 매개 변수를 사용할 수 있습니다 . 다음은 몇 가지 예제 코드입니다.

case class Person(name: String, age: Int, children: List[Person] = List()) {
  def addChild(p: Person) = copy(children = p :: this.children)
}

val parent = Person(name = "Bob", age = 55)
  .addChild(Person("Lisa", 23))
  .addChild(Person("Peter", 16))

음, Scala 2.8에서 이것을 고려하십시오.

case class Person(name: String, 
                  married: Boolean = false, 
                  espouse: Option[String] = None, 
                  children: Set[String] = Set.empty) {
  def marriedTo(whom: String) = this.copy(married = true, espouse = Some(whom))
  def addChild(whom: String) = this.copy(children = children + whom)
}

scala> Person("Joseph").marriedTo("Mary").addChild("Jesus")
res1: Person = Person(Joseph,true,Some(Mary),Set(Jesus))

물론 여기에는 문제가 있습니다. 예를 들어, 만드는 시도 espouse하고 Option[Person], 그리고 서로 결혼 한 두 사람을 받고. 나는 생성자 private var및 / 또는 private생성자 및 공장에 의존하지 않고는 그것을 해결할 방법을 생각할 수 없습니다 .


다음은 몇 가지 추가 옵션입니다.

옵션 1

구현 자체를 변경 가능하게 만드 되 변경 가능 및 불변에 노출되는 인터페이스를 분리하십시오. 이것은 Swing 라이브러리 디자인에서 가져온 것입니다.

public interface Foo {
  X getX();
  Y getY();
}

public interface MutableFoo extends Foo {
  void setX(X x);
  void setY(Y y);
}

public class FooImpl implements MutableFoo {...}

public SomeClassThatUsesFoo {
  public Foo makeFoo(...) {
    MutableFoo ret = new MutableFoo...
    ret.setX(...);
    ret.setY(...);
    return ret; // As Foo, not MutableFoo
  }
}

옵션 2

If your application contains a large but pre-defined set of immutable objects (e.g., configuration objects), you might consider using the Spring framework.


It helps to remember there are different kinds of immutability. For your case, I think "popsicle" immutability will work really well:

Popsicle immutability: is what I whimsically call a slight weakening of write-once immutability. One could imagine an object or a field which remained mutable for a little while during its initialization, and then got “frozen” forever. This kind of immutability is particularly useful for immutable objects which circularly reference each other, or immutable objects which have been serialized to disk and upon deserialization need to be “fluid” until the entire deserialization process is done, at which point all the objects may be frozen.

So you initialize your object, then set a "freeze" flag of some sort indicating that its no longer writable. Preferably, you'd hide the mutation behind a function so the function is still pure to clients consuming your API.


You could also make the immutable objects expose methods that look like mutators (like addSibling) but let them return a new instance. That's what the immutable Scala collections do.

The downside is that you might create more instances than necessary. It's also only applicable when there exist intermediate valid configurations (like some node without siblings which is ok in most cases) unless you don't want to deal with partially built objects.

For example a graph edge which has no destination yet isn't a valid graph edge.


Consider four possibilities:

new Immutable(one, fish, two, fish, red, fish, blue, fish); /*1 */

params = new ImmutableParameters(); /*2 */
params.setType("fowl");
new Immutable(params);

factory = new ImmutableFactory(); /*3 */
factory.setType("fish");
factory.getInstance();

Immutable boringImmutable = new Immutable(); /* 4 */
Immutable lessBoring = boringImmutable.setType("vegetable");

To me, each of 2, 3, and 4 is adapted to a difference situation. The first one is hard to love, for the reasons cited by the OP, and is generally a symptom of a design that has suffered some creep and needs some refactoring.

What I'm listing as (2) is good when there is no state behind the 'factory', whereas (3) is the design of choice when there is state. I find myself using (2) rather than (3) when I don't want to worry about threads and synchronization, and I don't need to worry about amortizing some expensive setup over the production of many objects. (3), on the other hand, is called forth when real work goes into the construction of the factory (setting up from an SPI, reading configuration files, etc).

Finally, someone else's answer mentioned option (4), where you have lots of little immutable objects and the preferable pattern is to get news ones from old ones.

Note that I'm not a member of the 'pattern fan club' -- sure, some things are worth emulating, but it seems to me that they take on an unhelpful life of their own once people give them names and funny hats.


Another potential option is to refactor to have fewer configurable fields. If groups of fields only work (mostly) with each other, gather them up into their own small immutable object. That "small" object's constructors/builders should be more manageable, as will the constructor/builder for this "big" object.


I use C#, and these are my approaches. Consider:

class Foo
{
    // private fields only to be written inside a constructor
    private readonly int i;
    private readonly string s;
    private readonly Bar b;

    // public getter properties
    public int I { get { return i; } }
    // etc.
}

Option 1. Constructor with optional parameters

public Foo(int i = 0, string s = "bla", Bar b = null)
{
    this.i = i;
    this.s = s;
    this.b = b;
}

Used as e.g. new Foo(5, b: new Bar(whatever)). Not for Java or C# versions before 4.0. but still worth showing, as it's an example how not all solutions are language agnostic.

Option 2. Constructor taking a single parameter object

public Foo(FooParameters parameters)
{
    this.i = parameters.I;
    // etc.
}

class FooParameters
{
    // public properties with automatically generated private backing fields
    public int I { get; set; }
    public string S { get; set; }
    public Bar B { get; set; }

    // All properties are public, so we don't need a full constructor.
    // For convenience, you could include some commonly used initialization
    // patterns as additional constructors.
    public FooParameters() { }
}

Usage example:

FooParameters fp = new FooParameters();
fp.I = 5;
fp.S = "bla";
fp.B = new Bar();
Foo f = new Foo(fp);`

C# from 3.0 on makes this more elegant with object initializer syntax (semantically equivalent to the previous example):

FooParameters fp = new FooParameters { I = 5, S = "bla", B = new Bar() };
Foo f = new Foo(fp);

Option 3:
Redesign your class not to need such a huge number of parameters. You could split its repsonsibilities into multiple classes. Or pass parameters not to the constructor but only to specific methods, on demand. Not always viable, but when it is, it's worth doing.

참고URL : https://stackoverflow.com/questions/2848938/building-big-immutable-objects-without-using-constructors-having-long-parameter

반응형