複雑なオブジェクトを優雅に作成するBuilderモード


要約:複雑なオブジェクトを作成する必要がある場合、静的ファクトリまたはコンストラクタを使用する方法は、共通の限界があるため、多くのオプションパラメータにうまく拡張できません.つまり、柔軟性が悪いです.では、このようなクラスに対して、オブジェクトをどのように作成すればいいのでしょうか.本論文では,オーバーラップコンストラクタモード,JavaBeansモード,Builderモードの3つの解決策を列挙し,上記の3つの方法を具体例によって敷き詰め,比較し,Builderモードの理解を真に支援した.
著作権声明:
本文原创作者:本呆子Rico作者ブログアドレス:http://blog.csdn.net/justloveyou_/
一.動機
複雑なオブジェクトを作成する必要がある場合、静的ファクトリまたはコンストラクタを使用する方法は、共通の限界があるため、多くのオプションパラメータにうまく拡張できません.Personクラスで人を記述することを考慮すると、名前、性別、誕生日、メールボックスなどの必要な属性のほかに、身長、学歴、あだ名、体重、通信アドレスなど、オプションの属性がたくさんあります.このようなクラスでは、オブジェクトをどのように作成すればいいのでしょうか.一般的なオーバーラップコンストラクタモードでもJavaBeansモードでも、このような問題をうまく解決することはできませんが、本稿で重点的に述べたBuilderモードは、このような問題を解決するための剣です.Builderモードがもたらすメリットをより深く理解するために、まずオーバーラップコンストラクタモードとJavaBeansモードをそれぞれ採用して、上記の問題を解決します.
二.オーバーラップコンストラクタモードを使用した複雑なオブジェクトの作成
このモードでは、最初のコンストラクタには必要なパラメータしかありません.2番目のコンストラクタにはオプションパラメータがあり、3番目のコンストラクタには2つのオプションパラメータがあります.これにより、最後のコンストラクタには次のようにすべてのパラメータが含まれます.
public class Person {
    private String name;    // required
    private String sex;     // required
    private Date date;      // required
    private String email;       // required

    private int height;     // optional
    private String edu;     // optional
    private String nickName;     // optional
    private int weight;     // optional
    private String addr;     // optional

    public Person(String name, String sex, Date date, String email) {
        this(name, sex, date, email, 0);
    }

    public Person(String name, String sex, Date date, String email, int height) {
        this(name, sex, date, email, height, null);
    }

    public Person(String name, String sex, Date date, String email, int height, String edu) {
        this(name, sex, date, email, height, edu, null);
    }

    public Person(String name, String sex, Date date, String email, int height, String edu, String nickName) {
        this(name, sex, date, email, height, edu, nickName, 0);
    }

    public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int
            weight) {
        this(name, sex, date, email, height, edu, nickName, weight, null);
    }

    public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int
            weight, String addr) {
        this.name = name;
        this.sex = sex;
        this.date = date;
        this.email = email;
        this.height = height;
        this.edu = edu;
        this.nickName = nickName;
        this.weight = weight;
        this.addr = addr;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", date=" + date +
                ", email='" + email + '\'' +
                ", height=" + height +
                ", edu='" + edu + '\'' +
                ", nickName='" + nickName + '\'' +
                ", weight=" + weight +
                ", addr='" + addr + '\'' +
                '}';
    }
}

このモードを使用してオブジェクトを作成する場合、いくつかの不足点があります.
  • 柔軟性が非常に悪い:クライアントが特定の名前、性別、誕生日、メールボックス、体重の人を作成したい場合、彼は次のコンストラクション関数を呼び出し、無意識に設定したくないパラメータを「強制」します.
  •     public Person(String name, String sex, Date date, String email, int height, String edu, String nickName, int
                weight) {
            this(name, sex, date, email, height, edu, nickName, weight, null);
        }
  • コードの作成と読み取りが困難です.プロパティが多い場合、コードは醜く見えるだけでなく、エラーが発生しやすいです.クライアントがパラメータリストの2つのパラメータの順序を誤って逆転した場合(たとえば、パラメータ「email」と「edu」を逆転した場合)、コンパイラもエラーは発生しませんが、実行時にエラーの動作が発生し、このエラーは発見しにくくなります.

  • 三.JavaBeansモードを使用した複雑なオブジェクトの作成
    この場合、JavaBeansモードに助けを求めてこれらの問題を回避することができますが、同じように新しい問題をもたらすこともあります.同じ例では、JavaBeansモードを使用すると、コードは次のようになります.
    public class Person {
        private String name;    // required
        private String sex;     // required
        private Date date;      // required
        private String email;       // required
    
        private int height;     // optional
        private String edu;     // optional
        private String nickName;     // optional
        private int weight;     // optional
        private String addr;     // optional
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        public void setDate(Date date) {
            this.date = date;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public void setHeight(int height) {
            this.height = height;
        }
    
        public void setEdu(String edu) {
            this.edu = edu;
        }
    
        public void setNickName(String nickName) {
            this.nickName = nickName;
        }
    
        public void setWeight(int weight) {
            this.weight = weight;
        }
    
        public void setAddr(String addr) {
            this.addr = addr;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", sex='" + sex + '\'' +
                    ", date=" + date +
                    ", email='" + email + '\'' +
                    ", height=" + height +
                    ", edu='" + edu + '\'' +
                    ", nickName='" + nickName + '\'' +
                    ", weight=" + weight +
                    ", addr='" + addr + '\'' +
                    '}';
        }
    }

    この方法は柔軟性を保証しますが、エラーも発生しにくいです.例えば、次のようにします.
            Person p2 = new Person();
            p2.setName("livia");
            p2.setSex("girl");
            p2.setDate(new Date());
            p2.setEmail("[email protected]");
            p2.setHeight(163);
            p2.setEdu("NCU");
            p2.setNickName("pig");
            p2.setWeight(100);
            p2.setAddr("   ");
            System.out.println(p2);

    しかし、それ自体にもこのような固有の欠点があります.例えば、
  • Setterの存在は、可変クラスになる可能性を妨げています.これにより、同時環境では、スレッドのセキュリティを考慮せざるを得ません.
  • コードが醜く、オブジェクトが不一致になりやすい:上にオブジェクトを作成する方法も醜いと同時に、オブジェクトの構築プロセスがいくつかの関数呼び出しに分かれているため、オブジェクトが不一致になりやすい.

  • 四.Builderモードを使用した複雑なオブジェクトの作成
    Builderモードを使用して複雑なオブジェクトを作成すると、上記の2つの方法の欠点を回避できるだけでなく、それぞれの利点を兼ねることができます.このモードの内包は,希望するオブジェクトを直接生成するのではなく,クライアントに必要なすべてのパラメータを用いてBuilderオブジェクトを構築させ,それに基づいてSetterのようなメソッドを呼び出して各オプションパラメータを設定し,最後に無参のbuild()メソッドを呼び出すことで可変オブジェクトを生成することである.一般的に、Builderは構築されたクラスの静的メンバークラスです.コードは次のとおりです.
    public class Person {
        private final String name;    // required
        private final String sex;     // required
        private final Date date;      // required
        private final String email;       // required
    
        private final int height;     // optional
        private final String edu;     // optional
        private final String nickName;     // optional
        private final int weight;     // optional
        private final String addr;     // optional
    
        //      ,  Person          Builder
        private Person(Builder builder) {
            this.name = builder.name;
            this.sex = builder.sex;
            this.date = builder.date;
            this.email = builder.email;
            this.height = builder.height;
            this.edu = builder.edu;
            this.nickName = builder.nickName;
            this.weight = builder.weight;
            this.addr = builder.addr;
        }
    
        public static class Builder{
            private final String name;    // required,  final  
            private final String sex;     // required,  final  
            private final Date date;      // required,  final  
            private final String email;       // required,  final  
    
            private int height;     // optional,   final  
            private String edu;     // optional,   final  
            private String nickName;     // optional,   final  
            private int weight;     // optional,   final  
            private String addr;     // optional,   final  
    
            public Builder(String name, String sex, Date date, String email) {
                this.name = name;
                this.sex = sex;
                this.date = date;
                this.email = email;
            }
    
            //   Builder    ,    
            public Builder height(int height){
                this.height = height;
                return this;
            }
    
            //   Builder    ,    
            public Builder edu(String edu){
                this.edu = edu;
                return this;
            }
    
            //   Builder    ,    
            public Builder nickName(String nickName){
                this.nickName = nickName;
                return this;
            }
    
            //   Builder    ,    
            public Builder weight(int weight){
                this.weight = weight;
                return this;
            }
    
            //   Builder    ,    
            public Builder addr(String addr){
                this.addr = addr;
                return this;
            }
    
            //   Builder    Person  ,         Person  
            public Person build(){
                return new Person(this);
            }
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", sex='" + sex + '\'' +
                    ", date=" + date +
                    ", email='" + email + '\'' +
                    ", height=" + height +
                    ", edu='" + edu + '\'' +
                    ", nickName='" + nickName + '\'' +
                    ", weight=" + weight +
                    ", addr='" + addr + '\'' +
                    '}';
        }
    }

    Personオブジェクトを作成するには、次のようにします.
            Person.Builder builder = new Person.Builder("rico", "boy", new Date(), "[email protected]");
            Person p1 = builder.height(173).addr("   ").nickName("   ").build();

    この方法でオブジェクトを作成すると、柔軟で読みやすく、エラーが発生しにくいことが明らかになりました.総じて、このモードは以下の特徴を有する.
  • Personクラスの構築方法はプライベートです.つまり、クライアントはUserオブジェクトを直接作成できません.
  • Personクラスは可変ではありません:すべての属性はfinalによって修飾され、構造方法でパラメータ値を設定し、settersメソッドを外部に提供しません.
  • Builderモードの高可読性:Builderモードはチェーン呼び出しを使用し、より可読性が高い.
  • Builderオブジェクトとターゲットオブジェクトの異同:PersonとBuilderは共通の属性を持ち、Builder内部クラス構築方法では必須のパラメータのみが受信され、これらの必須のパラメータのみがfinal修飾子を使用する.

  • 五.Builderモードのパラメータ制約とスレッドセキュリティ
    Personオブジェクトは可変ではないため、スレッドが安全であることを知っています.ただし、Builderオブジェクトにはスレッドセキュリティはありません.したがって、Personオブジェクトのパラメータに制約を加える必要がある場合は、builder()メソッドで作成したPersonオブジェクトを検証することができます.すなわち、builder()メソッドを次のように書き換えることができます.
        public Person build(){
            Person person = new Person(this);
            if (!"boy".equals(person.sex)){
                throw new IllegalArgumentException("          !");
            }else{
                return person;
            }
        }

    特に注意しなければならないのは、Builderオブジェクトがスレッドセキュリティではないため、Builderオブジェクトに対してパラメータチェックを行うのではなく、Personオブジェクトに対してパラメータチェックを行うことです.つまり、次のコードにはスレッドセキュリティの問題があります.
        public Person build(){
            if (!"boy".equals(this.sex)){
                throw new IllegalArgumentException("          !");
            }else{
                return new Person(this);
            }
        }

    六.まとめ
    (1). Builderモードの適用シーン
    オブジェクトの属性は多く、一般的に5つ以上の属性を有し、特に多くのパラメータがオプションである場合.
    (2). BuilderモードとオーバーラップコンストラクタモードおよびJavaBeansモードの比較
    オーバーラップコンストラクタモードに比べて、Builderモードを使用するコードは読みやすく、作成しやすく、JavaBeansモードよりも安全です.
    (3). 本明細書で説明するBuilderモードとGOF古典的Builderモードの関係
    本明細書で述べたBuilderモードは、Directorを省略したGOF古典的なBuilderモードの簡略化版と見なすことができ、構造がより簡単である.特に、多くのフレームワークソースコードにおいて、Builderモードに関するアプリケーションの多くは、古典的なGOFのBuilderモードではなく、Hibernate中国SessionFactoryの作成など、本明細書で検討した形式である.
    GOF古典的Builderモードは実際にはあまり使用されていないため,本明細書では後述しない.
    七.その他
    Java内部クラスの詳細については、筆者の「Java内部クラス概要」に移動してください.
    同時プログラミングとスレッドセキュリティの問題については、筆者の「Java同時プログラミング学習ノート」のコラムに移動してください.
    参照:
    設計モードのBuilderモード