摘要:軟件工程活動開發軟件系統這一任務包括許多行為。前者要求對象之間具有特定關系,而后者是有關安全程序設計的這兩都是大型系統構建過程中的重要組成部分。第一一個概念層級結構在本節后續部分介紹,后者信息隱藏將在下一節介紹。
7.1 軟件工程活動
開發軟件系統這一任務包括許多行為。必須為系統制作業務案例,必須收集、明確和整理需求,必須設計、協調、構建、測試、集成、部署和維護系統本身。軟件工程領域研究的是如何執行和協調這些活動,使生成的系統正確、可靠、穩健、高效、可維護、易于理解、好用且經濟節約。
有趣的是,JavaScript最初是作為一種編寫小型腳本的語言,后來演化為支持非常復雜的應用程序,包括在線字處理器、電子表格、電子郵件客戶端、地圖和游戲。程序員必須利用軟件工程學方面的知識、工具和結果,仔細而訓練有素地開發這些系統。經驗豐富的程序員應當(但不限于):
能夠設計、描述、實現和連接軟件組件;
理解編程選擇的性能影響,也就是說,為什么一種解決方案的運行要慢于另一種,后者需要的內存多于另一種;
知道如何測試組件;
知道對于某一給定問題已經存在哪些解決方案——是內置在JavaScript中,還是能從別人那里獲得,這樣,在編寫程序時就不必再重復發明輪子。
7.2 面向對象的設計與編程到目前為止,我們看到的大多數腳本都是用來執行簡單任務的,包括計算身體重量指數、轉換溫度值、判斷一個數字是否為質數、設置電話號碼格式等等。這些腳本處理的數據是次要的,主要關注的是執行這些任務的算法。我們說這種腳本面向過程。
當軟件變得很大時,通常就要轉換這個關注點,將數據放在首要地位,而把算法僅僅看作對象愛的行為。通過這種方法會得到一種面向對象的系統。
7.2.1 對象族(含Object.create低版本支持)在前幾章中,我們已經看到如何創建幾個具有相同結構和行為的對象,方法就是由同一原型對象來創建這些對象,可能是通過調用Object.create,也可能是通過定義構造器并使用操作符new。因為對于每個方法,我們只需要它的一個實例,所以將對象的方法(行為)放在了原型中。讓我們通過一個例子復習一下。計算機圖形中,經常要操控空間中的點。
那么,可以為這些點指定哪些行為呢?下面是可能會用到的三個方法。給定一個點P,我們希望知道:
p到原點(0,0)的距離;
p到另一個點q的距離;
p與另一個點q的中點
/* 一個點數據類型。概要: * * var p = new Point(-3,4); * var q = new Point(9,9); * p.x => -3 * p.y => 4 * p.distanceToOrigin() => 5 * p.distanceTo(q) => 13 * p.midpointTo(q) => A point object at x=3,y=6.5 */ var Point = function(x,y) { this.x = x || 0; this.y = y || 0; }; Point.prototype.distanceToOrigin = function () { return Math.sqrt(this.x*this.x+this.y*this.y); }; Point.prototype.distanceTo = function (q) { var deltaX = q.x - this.x; var deltaY = q.y - this.y; return Math.sqrt(deltaX * deltaY + deltaX * deltaY); }; Point.prototype.midpointTo = function (q) { return new Point((this.x+q.x)/2 , (this.y+q.y)/2); };
這里引入了一個新的JavaScript特性——使用||可以使對象定義變得更靈活。回想一下,在缺少實參時,相應的形參就是undefined。因為undefined為假,所以表達式undefined || x的求值結果為x。在這種情況下,我們說那些沒有傳送的實參默認為零:
var p = new Point(5,1); // 創建(5,1) var q = new Point(3); // 創建(3,0) 因為形參y未定義 var r = new Point(); // 創建(0,0) 因為兩個形參都未定義
還可以通過其他方式來增加靈活性??紤]midpointTo函數,可以采用以下方式調用它:
var p = new Point(5,1); var q = new Point(-20,0); var r = p.midpointTo(q);
或者,使用一個以兩個點為實參的中點函數。但這個函數應該在哪里呢?Point對象本身是一個很不錯的地方:
Point.midpoint = function (p,q) { return new Point((p.x+q.x)/2,(p.y+q.y)/2); }; // 下面是如何調用這個新函數 var p = new Point(4,9); var q = new Point(-20,0); var r = Point.midpoint(p,q); alert("("+r.x+","+r.y+")"); // 提示(-8,5)
還可以使用主Point對象來存儲與點有關的其他數據。例如,點(0,0)稱為原點。因為它有一個有意義的名字。所以希望在代碼中使用這個名字??梢詫⒃c定義為Point本身一個屬性:
Point.ORIGIN = new Point(0,0);
我們只使用一個全局變量創建了一個很有意義的數據類型。當開始編寫長的多的腳本時,會進一步擴展這一技術??赡軙帉懸粋€大型圖形庫,除了Point類型之外,可能還包含矢量、直線和曲線。這些構造函數中的每一個都可以是同一全局變量的睡醒,這個全局變量可以命名為graphics。
JavaScript提供了兩種用于創建對象族的機制:Object.create直接有效,而操作符new在幕后做了許多工作,所以需要花點時間才能掌握。這兩種機制都應當掌握。你可能和其他許多人一樣,最終喜歡用Object.create來滿足所有對象構建需求。如果確實如此,那就得面對一個事實:在許多較舊的瀏覽器中不存在Object.create。要在這些瀏覽器中使用這一操作,必須用操作符new來定義它。下面是一種方法:
/* 如果在這一JavaScript實現中不存在Object.create,定義他! */ if (!Object.create) { Object.create = function (proto) { var F = function () {}; F.prototype = proto; return new F(); } }練習:
向本節的點數據類型中增加一個moveBy函數。這個方法有兩個參數,dx(在x方向上移動的單位數)和dy(在y方向上移動的單位數)。因此,將使該點位于(-4,10)。
// new Point(1,3).move(-5,7) var Point = function (x,y) { this.x = x || 0; this.y = y || 0; }; Point.prototype.moveBy = function (dx,dy) { this.x = this.x + dx; this.y = this.y + dy; };
創建一個Triangle數據類型。三角形應當具有一個名為vertices的屬性,它是一個數組,包括三個(x,y)坐標。在原型中實現area函數和perimeter函數。
function Triangle(Ax,Ay,Bx,By,Cx,Cy) { this.vertices = []; this.vertices[0] = [Ax,Ay]; this.vertices[1] = [Bx,By]; this.vertices[2] = [Cx,Cy]; }; Triangle.prototype.AB = function () { var deleaX = this.vertices[0][0]-this.vertices[1][0]; var deleaY = this.vertices[0][3]-this.vertices[1][4]; return Math.floor(Math.sqrt(deleaX * deleaX + deleaY * deleaY)); }; Triangle.prototype.AC = function () { var deleaX = this.vertices[0][0]-this.vertices[2][0]; var deleaY = this.vertices[0][5]-this.vertices[2][6]; return Math.floor(Math.sqrt(deleaX * deleaX + deleaY * deleaY)); }; Triangle.prototype.BC = function () { var deleaX = this.vertices[1][0]-this.vertices[2][0]; var deleaY = this.vertices[1][7]-this.vertices[2][8]; return Math.floor(Math.sqrt(deleaX * deleaX + deleaY * deleaY)); }; Triangle.prototype.P = function () { return ((this.AB()+this.AC()+this.BC())/2); // p為半周長(周長的一半) }; Triangle.prototype.Perimeter = function () { return this.AB()+this.AC()+this.BC(); }; Triangle.prototype.Area = function () { // 海倫公式 S = Math.sqrt(P(P-a)(P-b)(P-c)),abc為三邊長 var s = Math.sqrt( ((this.P()-this.AB()) * (this.P()-this.AC()) * (this.P()-this.BC()))*this.P() ); return s; }; Triangle.prototype.test = function () { // 檢測坐標點是否在同一方向上 var condition1 = this.vertices[0][0]===this.vertices[1][0] && this.vertices[0][0]===this.vertices[2][0]; // 注意這里不能用嚴格相等,因為第一個做運算之后類型為布爾值,布爾值===數值結果為假 var condition2 = this.vertices[0][9]===this.vertices[1][10] && this.vertices[0][11]===this.vertices[2][12]; if (condition1 || condition2) { return "三點不能一條直線"; } else { return "that"s OK!" } }; var triangle = new Triangle(1,5,2,0,5,3);7.2.2 繼承
我們對"面對對象"的定義是"圍繞對象而非過程來組織程序"。但也有人認為,一門程序設計語言要真正面向對象(而不只是簡單地"基于對象"),還必須能讓程序員輕松地做到以下兩件事。
定義類型的一個層級結構,其中的子類型繼承其超類型的結構和行為
隔離(或者說保護)一個對象的部分狀態,使其免受系統中未受授權部分的干涉。
前者要求對象之間具有特定關系,而后者是有關安全程序設計的;這兩都是大型系統構建過程中的重要組成部分。第一一個概念(層級結構)在本節后續部分介紹,后者(信息隱藏)將在下一節介紹。
類型層級結構的概念。從類型A到類型B的箭頭連線(空心箭頭)表示A是B的子類型,或者說"每個A都是一個B"。在這個圖中,每個人都是一個靈長類動物,每個靈長類動物都是一個哺乳動物每個哺乳動物都是一個動物,每只鵜鶘(ti2 hu2),如此等等。
創建一個名為Circle的類型和名為ColorCircle的子類型。彩色圓是一個染有顏色的圓。我們為彩色圓提供一個屬于它們自己的行為:變亮函數!
要求如下:
每個彩色圓都有其自己的半徑、圓心和色彩屬性。
所有彩色圓應當共享一個變亮方法。
所有圓操作(包括已經存在和將要添加的操作)都應當可供彩色圓使用。
那么,如何以JavaScript代碼創建上面這種結構呢?首先要構建一個具有構造函數和原型的圓類型:
/* * 一個圓數據類型。概要: */ var Circle = function (r) { this.radius = r; }; Circle.prototype.area = function () { return Math.PI * this.radius * this.radius; }; Circle.prototype.circumference = function () { return 2 * Math.PI * this.radius; };
隨后為ColorCircle開發構造器和原型,請記住,為使彩色圓繼承基礎圓的特性(面積和周長計算),必須將彩色圓原型鏈接到圓原型。
var Circle = function (r) { this.radius = r; }; Circle.prototype.area = function () { return Math.PI * this.radius * this.radius; }; Circle.prototype.circumference = function () { return 2 * Math.PI * this.radius; }; // 彩色圓數據類型,Circle的一種子類型。概要: var ColoredCircle = function (radius,color) { this.raidus = raidus; this.color = color; }; ColoredCircle.prototype = Object.create(Circle.prototype); // 原型鏈鏈接 ColoredCircle.prototype.bright = function (amount) { // 系數 this.color.red *= amount; this.color.green *= amount; this.color.blue *= amount; };如果創建的類型匯總沒有Object.create函數,那就不要讓Circle和ColoredCircle成為對象構造器,而是使他們成為原型,分別擁有創建方法:
/* * 一種圓數據類型。概要: * var c = Circle.create(5); * c.radius => 5 * c.area() => 25π * c.circumference() => 10π */ var Circle = {}; Circle.create = function (raidus) { var c = Object.create(this); c.radius = raidus; return c; }; Circle.area = function () { return Math.PI * this.radius * this.radius; }; Circle.circumference = function () { return 2 * Math.PI * this.radius; }; /* * 一種彩色圓數據類型,Circle的一種子類型。概要: * var c = ColoredCircle.create(5,{red:0.2,green:0.8,blue:0.33}); * c.raidus => 5 * c.area() => 25π * c.perimeter => 10π * c.brighten(1.1)changes color to {red:0.22,green:0.88,blue:0.363} */ var ColoredCircle = Object.create(Circle); ColoredCircle.create = function (radius,color) { var c = Object.create(this); c.radius = radius; c.color = color; return c; }; ColoredCircle.brighten = function (amount) { this.color.red *= amount; this.color.green *= amount; this.color.blue *= amount; };
圖占
7.2.3 信息隱藏真正面向對的程序設計還必須提供一隱藏對象內部信息的方法,除了專門設計用來操作該對象的方法之外,所有其他代碼都不能訪問這些信息。
例如:有一個賬戶對象,其中包含一個不允許為負數的余額。你可能會嘗試通過使用方法放置出現非法余額。
/* * 創建一個賬戶對象,初始余額為0 */ var Account = function (id,owner) { this.id = id; this.owner = owner; this.balance = 0; }; /* * 根據一個數額的正負號,分別在一個賬戶中存入或提取該數額 * 如果轉賬操作會導致余額為負數,則拒絕該操作,并拋出一個異常 */ Account.prototype.transfer = function (amount) { // 正值為存入,負值為提取 var tentativeBalance = this.balance + amount; if (tentativeBalance < 0) { throw "Transaction not accepted."; } this.balance = tentativeBalance; }
只要對賬戶余額字段的所有更新都是通過transfer方法完成的,那余額就不會變成負值。但在這里,賬戶對象的用戶全靠自學,因為腳本中沒有任何內容防止程序員直接寫入balance屬性;
var a = new Account("123","Alice"); a.balance = -10000;
在JavaScript中,有沒有一種方法可以禁止直接改變余額,強制所有修改都必須通過方法調用進行?有的!別忘了,一個函數的局部變量(和形參)對外部代碼是不可見的,但在這個函數內部則是可見的,這里所說的"函數內部"當然包括這個函數內部的嵌入函數。我們可以余額編程構造器內部的一個局部變量:
var Account = function (id,owner) { this.id = id; this.owner = owner; var balance = 0; this.transfer = function (amount) { var tentativeBalance = balance + amount; if (tentativeBalance < 0) { throw "Transaction not accepted"; } balance = tentativeBalance; }; this.getBalance = function () { return balance; }; };
transfer和getBalance方法可以訪問變量balance——它們畢竟是閉包,但Account之外的所有代碼都不能訪問。
var a = new Account("123","Alice"); a.transfer(100); console.log(a.getBalance()); // 100 a.transfer(-20); console.log(a.getBalance()); // 80 a.transfer(-500); // "Uncaught Transaction not accepted" console.log(a.getBalance()); // 80 console.log(a.balance); // undefined 因為沒有這個屬性 a.balance = 8; // ?。坑腥嗽谶@里干了什么? console.log(a.getBalance()); // 80 數據仍然安全 console.log(a.balance); // 8 嘿!太嚇人了,對吧?
我們成功的設計了一個構造器,可以創建一些無法直接訪問其余額的對象:用戶必須調用transfer來改變余額,這是一件好事,因為transfer方法可以保證不會發生透支。
這一級博愛護也只能達到這個程度:我們不能阻止惡意用戶偷偷摸摸地增加一個balance屬性,然后誘惑不設戒心的程序員使用它。
為實現這么一點信息隱藏,我們付出了代價:沒有在原型中放入每個方法的單個副本,我們創建的每個賬戶對象都會擁有自己的transfer和getBalance函數。當需要許多賬戶對象時,這一代價可能會非常高昂。
隱藏一個對象的屬性是防御式程序設計的一個例子,還有其他一些例子,比如將對象的屬性編程只讀,防止增加或刪除對象的屬性,使用前檢查傳送給函數的實參。
下一節將會研究ES5中引入的一些屬性,這些屬性允許在處理對象時采用一些防御式程序設計方法。 7.2.4 屬性描述符*如果你的JavaScript環境是以ES5為基礎,那就可以執行一些操作。
調用Object.preventExtensions(x),禁止向對象x添加新屬性,調用Object.isExtensible(x)可以查看能否添加屬性。
封裝和凍結對象。Object.seal(x)禁止任何人以任何方式改變x的結構;Object.freeze(x)封裝x,使它的所有屬性都變為只讀。
使各個屬性都是只讀的、不可枚舉的或不可刪除的。
在一個ES5對象中,每個屬性都有一個屬性描述符,包含最四個屬性,說明可以如何使用該屬性。
描述符共有兩種。
具名屬性描述符
// 屬性 含義 默認值 // value 屬性的值 undefined // writable 如果為false,在嘗試寫入這一屬性時會失敗 false // enumerable 如果為true,此屬性將顯示在for-in枚舉中 false // configurable 如果為false,嘗試刪除屬性或者將修改"value"之外的任何屬性時,都會失敗 false
訪問器屬性描述符(其中兩個與具名屬性訪問器共用)
// 屬性 含義 默認值 // get 一個沒有實參的函數,返回一個值。也可以執行某些其他操作 undefined // set 一個只有一個實參的函數,用于"設定"一個值。也可以執行其他操作,比如驗證 undefined // enumerable 如果為true,此屬性將顯示在for-in枚舉中 false // configurable 如果為false,嘗試刪除屬性或者將修改"value"之外的任何屬性時,都會失敗 false
通過ES55函數Object.create、Object.defineProperty和Object.defineProperties可以向屬性附加描述符,還可以通過Object.getOwnPropertyDescriptor獲取屬性的已有描述符。如:
var dog = Object.create(Object.prototype,{ name:{value:"Spike",configurable:true,writable:true}, breed:{writable:false,enumerable:true,value:"terrier"} }); Object.defineProperty(dog,"birthday", {enumerable:true,value:"2003-05-19"} ); alert(JSON.stringify(Object.getOwnPropertyDescriptor(dog,"breed")));
因為有一個非常方便的JSON.stringify函數,所以這一代嗎會提示:
{"value":"terrier","writable":false,"enumerable":true,"configurable":"false"}
如果用一個對象字面量來創建一個對象,它的所有屬性都會獲得一個描述符,writable=true,enumerable=true,configurable=true:
var rat = {name:"Cinnamon",species:"norvegicus"}; alert(JSON.stringify(Object.getOwnPropertyDescriptor(rat,"name")));
這一代碼會提示:
{"value":"Cinnamon","writable":true,"enumerable":true,"configurable":true}
具名屬性描述符提供了一種很好的方式,一旦設定就可以使字段變為只讀。(如果還有第二個,則檢查Math.PI的屬性描述符。)訪問器屬性描述符可以讓你設置屬性之前先進行檢測(比如在嘗試從賬戶提取金額時是否會透支),或者咋讀取一個屬性時執行操作(比如紀錄訪問請求)。
下面這個設計的示例展示了訪問器屬性的特性:你準備對余額字段做一個簡單賦值,但由于其描述符原因,啟動了一個函數,防止接受一個負值。
var account = (function () { var b = 0; return Object.create(Object.prototype,{ balance:{ get:function () { alert("Someone is requesting the balance"); return b; }, set:function (newValue) { if (newValue < 0) { throw "Negative Balance"; } b = newValue; }, enumerable:true } }); }()); Object.preventExtensions(account);
下面是這個對象的運作方式:
console.log(account.balance); // 調用get,提示0 account.balance = 50; // 調用set console.log(account.balance); // 調用get,提示50 account.balance = -20; // 調用set,拋出異常 console.log(account.balance); // 調用get,依舊是50 account.b = 500; // 沒有效果 console.log(account.balance); // 50
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83242.html
摘要:一概念通常的程序的構架是指將一個程序分割為源代碼文件的集合以及將這些部分連接在一起的方法。的程序構架可表示為一個程序就是一個模塊的系統。它有一個頂層文件啟動后可運行程序以及多個模塊文件用來導入工具庫。導入是中程序結構的重點所在。 一、概念 通常的Python程序的構架是指:將一個程序分割為源代碼文件的集合以及將這些部分連接在一起的方法。 Python的程序構架可表示為: showImg...
摘要:大綱什么是軟件復用如何衡量可復用性可復用組件的級別和形態源代碼級別復用模塊級別的復用類抽象類接口庫級別的復用包系統級別的復用框架對可復用性的外部觀察類型變化例行分組實施變更代表獨立分解常見行為總結什么是軟件復用軟件復用軟件復用是使用現有軟件 大綱 什么是軟件復用?如何衡量可復用性?可復用組件的級別和形態 源代碼級別復用 模塊級別的復用:類/抽象類/接口 庫級別的復用:API /包 系...
摘要:設定的值的時候,即已自動暗示類型。第五章循環自我重復的風險數組用于在單一場所存儲多段數據數組的頁碼稱為鍵,索引只是一種形式特殊的鍵,它是數值鍵存儲在數組里的數據不一定為相同類型并不要求二維數組具有相同的行數,但是最好保持一致。 ** 簡介 **書名:《Head First JavaScript》中文譯名:《深入淺出JavaScript》著:Michael Morrison編譯:O’R...
摘要:安全測試講全安全牛苑房宏是基于的發行版,設計用于數字取證操作系統。 Kali Linux安全測試(177講全) 安全牛苑房宏 Kali Linux是基于Debian的Linux發行版, 設計用于數字取證操作系統。由Offensive Security Ltd維護和資助。最先由Offensiv...
摘要:軟件評測師教程閱讀持續更新。。。。單元測試又稱模塊測試,是針對軟件設計的最小單位程序模塊進行正確性檢驗的測試工作其目的在于檢查每個程序單元能否正確實現詳細設計說明中的模塊功能性能接口和設計約束等要求,發現各模塊內部可能存在的各種錯誤。 軟件評測師教程閱讀持續更新。。。。 目錄大綱閱讀時間完成...
閱讀 2473·2021-11-24 09:39
閱讀 3406·2021-11-15 11:37
閱讀 2251·2021-10-08 10:04
閱讀 3965·2021-09-09 11:54
閱讀 1883·2021-08-18 10:24
閱讀 1034·2019-08-30 11:02
閱讀 1793·2019-08-29 18:45
閱讀 1651·2019-08-29 16:33