值對象 與 引用對象

来源:http://www.cnblogs.com/taney/archive/2016/01/26/5159255.html
-Advertisement-
Play Games

值對象(value object)===================== 什麼是值對象 維基百科的定義 In computer science, a value object is a small object that represents a simple entity whose equ....


值對象(value object)

什麼是值對象

維基百科的定義

In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity
在電腦科學中,值對象是一種小型對象,它表示一個簡單的實體,該實體的相等性不取決於標識。

我們身邊就有一堆“值對象”,比如你現在從錢包掏一張紙幣,它就是典型的值對象,紙幣存在的意義在於表示一個價值。我們區分紙幣時並不是根據紙幣上那個唯一的編號,而是根據它表示的面值,具有相同面值的紙幣就是等價的,是可互換的。

這裡紙幣的編號就對應程式中對象的標識,紙幣的面值就對應程式中對象表示的值。

其它值對象的例子:ip地址、rgb顏色、地理坐標。

我的解理就是:值對象是用來表示一個量、一個值、一個信息的對象,它的狀態是靜態的;我們使用值對象時,關心的是它所表示的信息,而不是這個對象本身

值對象與不可變性(immutability)

值對象通常都會被設計成不可變的(immutable),比如.NET基類庫中的Int32、DateTime這些類型都是不可變的。
可能有人會對下麵的C#代碼提出疑問:

Int32 i = 1;
i = 2;

咦!這不是可變的嗎?註意這裡i是一個l-value(左值),是一個存放Int32類型值的變數,是線程棧上一個記憶體塊,是一個容器,相當於是錢包;下一句中只是把該錢包中的紙幣換了,並沒有修改紙幣的面值。像某日期.月份 = 12, 某整數.符號 = 負才叫修改了對象。

為什麼說值對象需要是不可變的

主要有以下原因:

  • 從理論上講值對象存在的意義就在於表示一個值,如果狀態修改了,那就是一個新的值,應該用新的對象表示。
  • 簡化編程、避免bug
    如果值對象可變,當它被共用時,一處修改可能會影響另一處,修改操作就會產生“副作用(side effect)”,如java中的java.util.Date是引用類型並且是可變的,下麵的代碼演示了使用它時可能犯的錯誤:

    //表示一個定時任務
    class Task{
      //設置執行時間
      public Date setStartDate(Date date){ this.startDate = date; }
      //獲取執行時間
      public Date getStartDate(){ return this.startDate; } //FIXME: 改成 return this.startDate.clone();
      //延遲執行時間
      void delay(int delayDays) {
        this.startDate.setDate(startDate.getDate() + delayDays);
      }
      private Date startDate;//執行時間
    }
    
    task1.setStartDate(new Date("2016/01/01");
    task2.setStartDate(task1.getTaskDate());
    
    task2.delay(5); //這裡本想修改task2的日期,但無意中影響到了task1

    這裡的問題在於我們關心的其實只是一個“日期值”而不是一個java.util.Date對象,所以在傳遞時應該是傳遞表示的日期值,而不是直接傳遞對象。註釋中的FIXME標註是一種解決辦法。

值對象必須要實現為不可變嗎?

但是如果把一個類實現為不可變的話,意味著修改一個成員就要創建一個新的對象,如果成員非常多的話,代碼寫起來會比較繁瑣。
有些語言支持值語義(value semantics),所謂“值語義”是指對象傳遞時是傳遞的它所表示的值(傳值),而不是傳遞對象本身(傳引用),這就意味著傳遞過程就是“複製”,比如C++預設就是值語義、C#中的struct等,因為是複製,所以值對象不會被共用,也就沒有了上面提到的那個問題,這種情況下,值對象可變的話也是完全可以的,但是值對象仍可以被“按引用”傳遞,所以把值對象實現為不可變是最保險的手段。

值對象的實現

  • C++
    C++預設就是值語義,如果類狀態較簡單,則可以不做任何特殊處理。如果比較複雜,比如成員是指針或引用,則就要實現生命周期管理函數
  • C#
    • 如果類狀態較簡單,可直接用struct,因為struct正好支持值語義
    • 如果狀態較複雜則用class,並且從介面上把它設計成不可變,比如:public readonly的屬性,提供一個能夠初始化所有成員的構造方法 或 提供修改狀態的方法,但實際不修改本對象的狀態,而是構建並返回一個具有的新狀態的新對象。
  • java
    同C#中的class

其它

struct與class

不管是C++和C#中的struct關鍵字,還是ruby中的Struct::new都是趨向用於定義簡單的複合類型,所以struct適合用來定義沒有複雜行為和狀態的值對象。而class更趨向用於定義具有豐富邏輯、複雜狀態的對象類型,struct、class和值對象、引用對象並不是一一對應的關係。

特殊的值對象 - 字元串

在我看來字元串是值對象,理由很簡單:它的相等性取決於它的狀態
雖然它本質是一個字元容器,但我們更多的是用它來進行比較,輸出等等,而不是對裡面的字元進行“增刪改查”,所以從它的用途來看,它是一個靜態的字元序列

那麼問題來了,為什麼在C#和java中字元串都是引用類型呢?
現階段我是這麼認為的:

  • “值類型”,“引用類型”這些只是語言/平臺實現中的術語,“值對象”、“引用對象”是語言無關的,是對象設計思想,所以不是說被實現為“引用類型”它就不是值對象了
  • 字元串本質上是字元容器,大小不定,不像Int32固定占4位元組,且無法在編譯時確定(當然字面量除外),如String str = new String('a', 10),因此無法分配於棧上
    雖然在C#中也完全可以把字元串實現為struct,在內部動態分配字元數組,但由於這樣內部實現會比較複雜,而struct更傾向於用來定義簡單的複合類型。

DTO(Data transfer object)

它和值對象的區別在於:值對象可以是一個domain model,可以擁有邏輯;而DTO被用於打包其它對象的狀態,在layer間傳遞,是一種貧血模型,它沒有邏輯。

引用對象(reference object)

什麼是引用對象?

引用對象是相等性取決於它的標識的一種對象。

為什麼要“相等性取決於標識”?因為在使用引用對象時,我們關心的是這個對象本身,在傳遞過程中,需要傳遞同一個對象,因此就把對象的“引用(即標識、通常是記憶體地址)”傳遞過去,這也就是“引用語義(reference semantics)”。

我們實際寫程式中,使用對象的目的在於映射問題域中的事物,因此基本關心的是對象本身,所以使用引用對象會相對比較多。

實現

  • 在C#和java中用class定義的類型叫“引用類型”,具有引用語義,創建的對象天生就是引用對象
  • 在C/C++中通常通過動態分配記憶體和傳遞指針實現

指針 與 引用類型

  • C/C++中的指針其實是一個unsigned int,表示一個記憶體地址。具體類型的指針類型如int *,類型名的用途在於解引用時知道數據的大小和數據表示的含義,我們可以用“強制類型轉換”以不同的大小和類型解讀一塊記憶體,非常靈活,但也容易出錯。
  • C#和java中的引用實際上是一個受限的、托管的、安全的指針,因為記憶體管理被GC接管了,它會釋放記憶體、整理記憶體碎片(就可能會移動分配在堆上的對象),所以就禁止開發人員通過這個指針獲取記憶體地址、計算偏移、釋放記憶體等,以防出錯,只能用它來“引用”托管堆上的對象。

參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • while迴圈在迴圈剛開始時,會計算一次“布爾表達式”的值,若條件為真,執行迴圈體,而對於後來每一次額外的迴圈,都會在開始前重新計算一次。註意:語句中應有使迴圈趨向於結束的語句,否則會出現無限迴圈——“死迴圈”。迴圈結構都由一下四個結構組成:初始化、條件判斷、迴圈體、迭代。計算1+2+3+……+10...
  • stringstream ss;//一次創建多次使用,需要進行clear()操作清除流狀態標記int i=0;while (i>str;//執行後,流狀態標誌位可能會被置1,需要清除,否則影響後續操作 ss.clear();//不會出現重覆現象 cout<<str<<endl; ...
  • 題目:Automated Telephone Exchangepoj URL:http://poj.org/problem?id=4011原題如下圖:題意:就是一個三位數減去兩個小於或等於99的數的差為0,滿足這個條件的數的個數。只要用標準輸入輸出就可以了。下麵是我Accepted的代碼: 1 #i...
  • /*首先~命名空間的存在是為瞭解決 引用不同類中命名衝突問題的*//*1.定義命名空間*/ namespace My; class A{}; namespace Your; class A{}; /*2.在同一個項目中引用命名空間*/ namespace My; class A{};...
  • 利用Python語言實現Grib數據可視化主要依靠三個庫——pygrib、numpy和matplotlib。pygrib是歐洲中期天氣預報中心(ECMWF)的GRIG API C庫的Python介面,通過這個庫可以將Grib數據讀取出來;numpy是Python的一種開源的數值計算擴展,這種工具可用...
  • 上述if語句的等值判斷,可以用switch來代替。註意每個case後面一般要添加break,表示當前這個case執行完了;防止出現case穿透,即繼續執行case,直到遇到break才跳出。下麵例子反過來利用了case穿透現象。【例子】JDK7.0新特性:增強switch在JDK7之前,switch...
  • #!/bin/env python # -*- coding: UTF-8 -*- # 必須以root許可權運行 import socket import sys import timeimport random from struct import * # 計算校驗和 def ...
  • 回到目錄dynamic這個動態類型早在.net3.5時就已經出現了,當時是伴隨的Linq一起讓我們認識的,但在使用時總覺得有點彆扭,因為它是internal的,所以不能跨程式集使用,這對於分層開發的我們來說顯然是不能接受的,所以把dynamic了冷落了很久,應該說是5年吧,哈哈,這幾天在睡覺時,突然...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...