1. 程式人生 > 其它 >Go使用內嵌型別實現額外保護——以防盜門示例

Go使用內嵌型別實現額外保護——以防盜門示例

技術標籤:Gogo


Go語言支援使用者自定義型別,而自定義型別中還可以再嵌入其他自定義的型別,被嵌入的型別被稱為“內嵌型別/內部型別”,而嵌入內部型別的就被成為“外部型別”。

通過嵌入型別,與內部型別相關的欄位、方法、識別符號等都會被外部型別所擁有,就像外部型別自己的一樣,這樣就達到了程式碼快捷複用組合的目的。

然而 內嵌型別的存在,也讓 共享pkg包中某個型別值,但禁止對該型別值進行復制(建立新地址、重新指向),僅以共享的方式傳遞 成為可能。 這句總結可以說是十分抽象。下面將防盜門的使用這一常見場景來進行示例講解:


一、需求分析

防盜門的生產和使用場景:

  1. 廠商可以生產防盜門,交到客戶手中(安裝門)時也會把門的密碼告訴客戶
  2. 建立的防盜門初始密碼都是不相同的
  3. 客戶輸入密碼,密碼正確的話可以開啟防盜門
  4. 客戶修改密碼,輸入新舊密碼,密碼正確的話可以修改防盜門密碼為新密碼
  5. 客戶關上防盜門

在設計功能之前,需要先思考下:

防盜門的本質是什麼?門。門的話,肯定就能夠“開門”和“關門”,但是如果直接由客戶來操作門,顯然沒有任何的安全性。這裡必須由防盜門來操作門,而客戶只能夠操作防盜門,從而保證了安全。門這一本質也由防盜門包含


二、功能設計

根據上面的需求,可以整理出以下功能點:

  • 對外-防盜門(結構:門指標型別,密碼)

    • 對外-建立:返回 防盜門指標變數 和其對應的密碼
    • 對外-修改密碼:輸入舊密碼和新密碼,正確的話將“防盜門”的密碼改為新密碼
    • 對外-開門:輸入密碼,正確的話呼叫“門”的開門方法
    • 對外-關門:此處可以不必實現。因為“門”是“防盜門”的內嵌型別,直接呼叫“門”的“關門”方法即可
  • 對內-門 (結構:門狀態)

    為防止對door型別值進行復制(建立新地址、重新指向),建立防盜門的方法提供給客戶,但方法內部初始化防盜門,便是實現“防盜門1用的門1,永遠不會被更改使用門2、門3,門1也永遠只有防盜門1使用”。

    • 對內-開門:修改門的狀態為“開”
    • 對外-關門:修改門的狀態為“關”

由需求上分析可知:“門”這一本質也由“防盜門”包含,那麼程式碼實現上便可將door門作為SecurityDoor的內嵌型別。

由於防盜門的修改密碼、開門等操作,都是針對“防盜門”本身的,並不是對其複製物件做處理。所以對應操作方法的接收者都是指標接收者

。同理,“門”的操作方法也都是指標接收者,且作為SecurityDoor結構的內嵌型別也是傳的*door

如果防盜門是可複製的,那我知道防盜門1的密碼,再複製防盜門2,這樣所有的防盜門密碼都是一樣,那就可以被亂開了。為防止此類情況發生,防盜門的建立方法便將指標型別直接返回給客戶進行處理(也可理解為返回了一個安裝在xxx地方的防盜門,相當於定義了防盜門不可複製。就算你想去複製,拿到的也是xxx地方的防盜門,即防盜門1本身,並不是建立了新的防盜門2,只是給防盜門1起了個別名叫做防盜門2)


三、程式碼實現

程式碼實現的目錄結構如下所示。將“防盜門”和“門”相關功能放在door_factory/door_factory.go檔案中,並作為包。main.go檔案為模擬客戶操作防盜門

在這裡插入圖片描述

main.go檔案:模擬客戶進行對防盜門的相關操作

package main

import (
	"door_factory"
	"fmt"
)

func main() {
	//建立一個防盜門,並獲得到初始密碼
	securityDoor, password := door_factory.CreateSecurityDoor()

	//防盜門是指標型別,指向的地址都是一樣的
	securityDoorCopy := securityDoor
	fmt.Printf("securityDoor的地址:%p\n", securityDoor)
	fmt.Printf("securityDoorCopy的地址:%p\n", securityDoorCopy)

	//輸入防盜門正確密碼開門,並顯示結果
	fmt.Println("輸入密碼開啟防盜門:", securityDoor.OpenDoor(password))

	//防盜門關門,使用的door的方法,但由於door是SecurityDoor的內嵌型別,其方法也可被SecurityDoor呼叫
	fmt.Println("關閉防盜門:", securityDoor.CloseDoor())

	//修改原密碼為"1234567890"
	newPassword := "1234567890"
	msg, success := securityDoor.ChangePassword(password, newPassword)
	fmt.Println(msg)

	//如果修改密碼成功,再輸入舊密碼,將會登入失敗
	if success {
		//輸入防盜門錯誤密碼開門,並顯示結果
		fmt.Println("輸入舊密碼開門:", securityDoor.OpenDoor(password))
	}

	/**
	結果輸出:
	securityDoor的地址:0xc000004660
	securityDoorCopy的地址:0xc000004660
	輸入密碼開啟防盜門: 門已開啟
	關閉防盜門: 門已關閉
	更新密碼成功
	輸入舊密碼開門: 密碼錯誤
	*/
}


door_factory/door_factory.go檔案:

package door_factory

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"math/big"
)

type SecurityDoor struct {
	*door    //door受保護,僅SecurityDoor可被外部呼叫
	password string
}

//建立一個防盜門
func CreateSecurityDoor() (*SecurityDoor, string) {
	password := createRandomNumber(10)
	securityDoor := SecurityDoor{
		door:     &door{isOpen: false},
		password: password,
	}
	return &securityDoor, password
}

//更改防盜門的密碼
func (securityDoor *SecurityDoor) ChangePassword(oldPwd string, newPwd string) (string, bool) {
	//如果密碼錯誤,返回操作錯誤
	if securityDoor.password != oldPwd {
		return "密碼錯誤", false
	}
	securityDoor.password = newPwd
	return "更新密碼成功", true
}

//門,外部無法呼叫,僅包內可用,但外部可以獲得當前門是否開啟的狀態
type door struct {
	isOpen bool
}

//防盜門開門方法-需要輸密碼,外部呼叫
func (securityDoor *SecurityDoor) OpenDoor(password string) string {
	if securityDoor.password != password {
		return "密碼錯誤"
	}
	return securityDoor.door.openDoor()
}

//door開門方法-僅包內可用
func (door *door) openDoor() string {
	if door.isOpen {
		return "門沒關,可以直接開"
	}
	door.isOpen = true
	return "門已開啟"
}

//door關門方法-外部也可呼叫
func (door *door) CloseDoor() string {
	if !door.isOpen {
		return "門本來就關著"
	}

	door.isOpen = false
	return "門已關閉"
}

//指定位數隨機數
func createRandomNumber(len int) string {
	var numbers = []byte{1, 2, 3, 4, 5, 7, 8, 9}
	var container string
	length := bytes.NewReader(numbers).Len()

	for i := 1; i <= len; i++ {
		random, err := rand.Int(rand.Reader, big.NewInt(int64(length)))
		if err != nil {

		}
		container += fmt.Sprintf("%d", numbers[random.Int64()])
	}
	return container
}


四、總結

  • 為了讓客戶能夠間接操作 “門” ,可以建立一個類似“防盜門”的結構體和對應方法,實現對內嵌型別操作的額外保護

  • 額外保護藉助了包的公開私有識別符號,如果在同個包,那麼也可對door進行宣告、初始化、賦值等操作

  • 由於door未公開,在包外無法通過結構字面量初始化該內部型別

  • 如果內嵌型別某方法公開,那麼接收者的識別符號提升為外部型別,如上面的"關門"操作,同樣可以訪問。欄位也是同理,還可被用於修改