ホームページ  >  記事  >  Java  >  5 分でプロトタイプ モードをマスターする

5 分でプロトタイプ モードをマスターする

Java后端技术全栈
Java后端技术全栈転載
2023-08-25 15:52:431043ブラウズ


#皆さんこんにちは、私は Lao Tian です。

今日は プロトタイプ パターンを共有します # # デザインパターンにあります。適切なライフ ストーリーや実際のプロジェクト シナリオを使用してデザイン パターンについて話し、最後にデザイン パターンを一文にまとめます。

5 分でプロトタイプ モードをマスターする

ストーリー 職場で、私は偶然、比較的美しいプログラマーの履歴書テンプレートをインターネットで見つけたので、クラス全員がその履歴書 (U ディスク) を狂ったようにコピーし始めました。一方で、冗談もあって、過去の履歴書を内容も名前も変えずにコピーした学生が何人かいて、期限内に面接官(学内採用面接官)に提出しました。全員がインターンシップに行き、中にはまだ仕事を探している人もいました。 その後、会社の面接官や他のクラスメートからのフィードバック: 全く同じ履歴書を何通か受け取りました。戻ってきたら、話し合った後、問題が何であるか全員が理解していました。私はそれをコピーしたことを認め、有害で恥ずかしいものです。

履歴書のコピーには 2 種類あります:

  • 1 つは、履歴書をコピーして自分の情報に変更する方法です。
  • もう 1 つは、内容を変更せずに履歴書をコピーする方法です。

プロトタイプ パターン定義

プロトタイプ パターンの定義プロトタイプ インスタンスを使用して作成するオブジェクトの種類、およびこのプロトタイプに対処して新しいオブジェクトを作成する

大まかな意味: プロトタイプ インスタンスを使用して作成するオブジェクトの種類を指定し、これらのプロトタイプをコピーして新しいオブジェクトを作成する。

プロトタイプ パターン: プロトタイプ パターン。これは作成パターンです。

呼び出し元は作成の詳細を知る必要はなく、オブジェクトを作成するためにコンストラクターを呼び出す必要もありません。

使用シナリオ

プロトタイプ モードには次の使用シナリオがあります:

  • #クラスの初期化はより多くのリソースを消費します
  • #new によって生成されたオブジェクトには非常に面倒なプロセス (データの準備、アクセス許可など) が必要です
  • コンストラクターはより複雑です
  • ループ本体で多数のオブジェクトが生成される場合
  • In Spring では、プロトタイプ パターンが広く使用されています。例:
  • scope='prototype'
  • いくつかのゲッターとセッターをファクトリ メソッドにカプセル化できます。次に、それを使用する人は、メソッドを呼び出すだけで、内部のゲッターとセッターがどのように処理されるかを知る必要はありません。
JDK

が提供する Cloneable インターフェイスを使用して、高速コピーを実現することもできます。 オブジェクトを作成する 4 つの方法:

新規、リフレクション、クローン作成、シリアル化

実際のケース このようなよくある状況に遭遇したことはありませんか? プロジェクトでは、データベース テーブルにマッピングされたエンティティ クラスをフロントエンドに返せないと規定されているため、通常は次のものが返されますフロントエンドへ: XxxVO、XxxBO、XxxDTO...

このとき、次のようなシーンが表示されますが、誰もが想像しているかもしれません。

以下は、データベース テーブルにマップされた

UserEntity

エンティティ クラスです。 <pre class="brush:php;toolbar:false;">public class UserEntity { private Long id; private String name; private Integer age; //....可能还有很多属性 //省略getter setter }</pre>フロントエンドまたは呼び出し元に返される UserVO エンティティ クラス。

public class UserVO {
    private Long id;
    private String name;
    private Integer age;
    //....可能还有很多属性
    //省略getter setter
}

現時点では、データベースから見つかった UserEntity を UserVO に変換して、フロントエンド (または呼び出し元) に返す必要があります。

public class ObjectConvertUtil {

    public static UserVo convertUserEntityToUserVO(UserEntity userEntity) {
        if (userEntity == null) {
            return null;
        }
        UserVo userVo = new UserVo();

        userVo.setId(userEntity.getId());
        userVo.setName(userEntity.getName());
        userVo.setAge(userEntity.getAge());
         //如果还有更多属性呢?
        return userVo;
    }
}

从这个util类中,我们可以看出,如果一个类的属性有几十个,上百个的,这代码量是不是有点恐怖?

于是,我们通常都会使用一些工具类来处理,比如常见有以下:

BeanUtils.copy();
JSON.parseObject()
Guava工具类
.....

这些工具类就用到了原型模式。

通过一个对象,创建一个新的对象。

也把原型模式称之为对象的拷贝、克隆。

其实对象的克隆分浅克隆和深克隆,下面我们就来聊聊浅克隆和深克隆。

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原来对象的属性所指向的对象的内存地址。
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

我们先来聊聊浅克隆,都喜欢由浅入深。

浅克隆

比如,我现在相对用户信息User进行克隆,但是User中有用户地址信息UserAddress属性。

以下是代码的实现:

//用户地址信息
public class UserAddress  implements Serializable{
    private String province;
    private String cityCode;

    public UserAddress(String province, String cityCode) {
        this.province = province;
        this.cityCode = cityCode;
    }
}
//用户信息
public class User implements Cloneable {
    private int age;
    private String name;
    //用户地址信息
    private UserAddress userAddress;

    //getter setter 省略

    @Override
    protected Object clone() throws CloneNotSupportedException { 
        return super.clone();
    }
}
//测试
public class UserTest {
    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setAge(20);
        user.setName("田维常");
        UserAddress userAddress = new UserAddress("贵州", "梵净山");
        user.setUserAddress(userAddress);

        User clone = (User) user.clone();

        System.out.println("克隆前后UserAddress比较:" + (user.getUserAddress() == clone.getUserAddress()));
    }
}

输出结果

克隆前后 UserAddress 比较:true

两个对象属性 UserAddress 指向的是同一个地址。

这就是所谓的浅克隆,只是克隆了对象,对于该对象的非基本类型属性,仍指向原来对象的属性所指向的对象的内存地址。

关系如下:

5 分でプロトタイプ モードをマスターする


深克隆

关于深克隆,我们来用一个很经典的案例,西游记里的孙悟空。一个孙悟空能变成n多个孙悟空,手里都会拿着一个金箍棒。

按照前面的浅克隆,结果就是:孙悟空倒是变成很多孙悟空,但是金箍棒用的是同一根。

深克隆的结果是:孙悟空变成了很多个,金箍棒也变成很多个根。

下面我们用代码来实现:

//猴子,有身高体重和生日
public class Monkey {
    public int height;
    public int weight;
    public Date birthday;
}

孙悟空也是猴子,兵器 孙悟空有个金箍棒:

import java.io.Serializable;
//孙悟空的金箍棒
public class JinGuBang implements Serializable{
    public float  h=100;
    public float  d=10;
    //金箍棒变大
    public void big(){
        this.h *=10;
        this.d *=10;
    }
    //金箍棒变小
    public void small(){
        this.h /=10;
        this.d /=10;
    }
}

齐天大圣孙悟空:

import java.io.*;
import java.util.Date;

//孙悟空有七十二变,拔猴毛生成一个金箍棒
//使用JDK的克隆机制,
//实现Cloneable并重写clone方法
public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {

    public JinGuBang jinGuBang;

    public QiTianDaSheng() {
        this.birthday = new Date();
        this.jinGuBang = new JinGuBang();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return this.deepClone();
    }

    //深克隆
    public QiTianDaSheng deepClone() {
        try {
            //内存中操作完成、对象读写,是通过字节码直接操作
            //与序列化操作类似
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream bis = new ObjectInputStream(bais);

            //完成一个新的对象,底层是使用new创建的一个对象
            //详情可以了解readObject方法
            QiTianDaSheng qiTianDaSheng = (QiTianDaSheng) bis.readObject();
            //每个猴子的生日不一样,所以每次拷贝的时候,把生日改一下
            qiTianDaSheng.birthday = new Date();
            return qiTianDaSheng;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    //浅克隆,就是简单的赋值
    public QiTianDaSheng shalllowClone(QiTianDaSheng target) {
        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        qiTianDaSheng.height = target.height;
        qiTianDaSheng.weight = target.weight;

        qiTianDaSheng.jinGuBang = target.jinGuBang;
        qiTianDaSheng.birthday = new Date();
        return qiTianDaSheng;

    }
}

接着我们就来测试一下:

public class DeepCloneTest {
    public static void main(String[] args) {
        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
        try {
            QiTianDaSheng newObject = (QiTianDaSheng) qiTianDaSheng.clone();
            System.out.print("深克隆后 ");
            System.out.println("金箍棒是否一直:" + (qiTianDaSheng.jinGuBang == newObject.jinGuBang));
            
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        QiTianDaSheng newObject=qiTianDaSheng.shalllowClone(qiTianDaSheng);
        System.out.print("浅克隆后 ");
        System.out.println("金箍棒是否一直:" + (qiTianDaSheng.jinGuBang == newObject.jinGuBang));
    }
}

输出结果为:

深克隆后 金箍棒是否一直:false
浅克隆后 金箍棒是否一直:true

结论

深克隆后每个孙悟空都有自己的金箍棒,而浅克隆后每个孙悟空用的金箍棒实质上还是同一根。

5 分でプロトタイプ モードをマスターする

总结

切记:深和浅,指的是克隆对象里的属性(引用类型)是否指向同一个内存地址。

为了更深刻的理解深克隆和浅克隆,我们回答文中的简历拷贝的故事。

  • 深いコピー: 履歴書をコピーし、履歴書の情報を自分のものに変更します。
  • ##浅いコピー: 履歴書をコピーし、履歴書の内容はまったく変更しません

利点:

  • Java プロトタイプ モードはメモリ バイナリ ストリーム コピーに基づいており、直接新しいものよりも優れたパフォーマンスを発揮します。 。
  • ディープ クローン作成を使用すると、オブジェクトの状態を保存し、古いコピーを保存して (クローンを作成して)、それを変更することができます。これは元に戻す機能として機能します。

欠点:

  • clone メソッドを構成する必要があり、変換中に既存のクラスを変更する必要があるため、違反します。閉鎖の「オープン」原則」。
  • オブジェクト間に複数のネストされた参照がある場合、各レイヤーを複製する必要があります。
プロトタイプパターンについて、その定義、利用シナリオ、実際の事例、シャロークローニング、ディープクローニング、メリットとデメリットなどの側面から包括的に解説しました。

以上が5 分でプロトタイプ モードをマスターするの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はJava后端技术全栈で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。