2013-07-31 24 views
16

Tôi có một struct trong một gói có các trường tư nhân:Trong Go, có cách nào truy cập các trường riêng của cấu trúc từ gói khác không?

package foo 

type Foo struct { 
    x int 
    y *Foo 
} 

Và gói khác (ví dụ, một thử nghiệm gói hộp trắng) cần truy cập vào chúng:

package bar 

import "../foo" 

func change_foo(f *Foo) { 
    f.y = nil 
} 

Có cách để khai báo bar là một loại gói "bạn bè" hoặc bất kỳ cách nào khác để có thể truy cập vào các thành viên riêng của foo.Foo từ bar, nhưng vẫn giữ riêng tư cho tất cả các gói khác (có thể là một cái gì đó trong unsafe)?

+0

Bạn không thể nĩa thư viện hiện tại và hiển thị các trường bạn cần sửa đổi? (lưu ý rằng bạn nên cho rằng chúng không được phơi sáng vì một lý do chính đáng) – elithrar

+0

@elithrar Tất cả đều là mã của tôi. Vì vậy ... có, chúng không được phơi sáng vì một lý do chính đáng; và vâng, tôi cần truy cập chúng. – Matt

Trả lời

28

một cách để đọc thành viên unexported sử dụng phản ánh

func read_foo(f *Foo) { 
    v := reflect.ValueOf(*f) 
    y := v.FieldByName("y") 
    fmt.Println(y.Interface()) 
} 

Tuy nhiên, cố gắng sử dụng y.Set, hoặc nếu không thiết lập hiện trường với phản ánh sẽ cho kết quả trong các mã hoảng loạn mà bạn' đang cố gắng thiết lập một trường chưa được công bố bên ngoài gói.

Tóm lại: các trường chưa được công bố sẽ không được báo cáo vì lý do, nếu bạn cần thay đổi chúng, hãy đặt thứ cần thay đổi trong cùng một gói hoặc hiển thị/xuất một số cách an toàn để thay đổi nó.

Điều đó nói rằng, vì lợi ích của việc trả lời đầy đủ các câu hỏi, bạn có thể làm điều này

func change_foo(f *Foo) { 
    // Since structs are organized in memory order, we can advance the pointer 
    // by field size until we're at the desired member. For y, we advance by 8 
    // since it's the size of an int on a 64-bit machine and the int "x" is first 
    // in the representation of Foo. 
    // 
    // If you wanted to alter x, you wouldn't advance the pointer at all, and simply 
    // would need to convert ptrTof to the type (*int) 
    ptrTof := unsafe.Pointer(f) 
    ptrTof = unsafe.Pointer(uintptr(ptrTof) + uintptr(8)) // Or 4, if this is 32-bit 

    ptrToy := (**Foo)(ptrTof) 
    *ptrToy = nil // or *ptrToy = &Foo{} or whatever you want 

} 

Đây là một ý tưởng thực sự, thực sự xấu. Nó không phải là di động, nếu bạn thay đổi kích thước, nó sẽ thất bại, nếu bạn sắp xếp thứ tự các trường trong Foo, thay đổi kiểu của chúng, hoặc kích thước của chúng hoặc thêm trường mới trước những trường đã tồn tại từ trước, chức năng này sẽ thay đổi đại diện mới cho dữ liệu ngẫu nhiên vô nghĩa mà không nói cho bạn biết. Tôi cũng nghĩ rằng nó có thể phá vỡ bộ sưu tập rác cho khối này.

Vui lòng, nếu bạn cần thay đổi trường từ bên ngoài gói hoặc viết chức năng để thay đổi từ trong gói hoặc xuất nó.

Edit: Đây là một cách hơi an toàn hơn để làm điều đó:

func change_foo(f *Foo) { 
    // Note, simply doing reflect.ValueOf(*f) won't work, need to do this 
    pointerVal := reflect.ValueOf(f) 
    val := reflect.Indirect(pointerVal) 

    member := val.FieldByName("y") 
    ptrToY := unsafe.Pointer(member.UnsafeAddr()) 
    realPtrToY := (**Foo)(ptrToY) 
    *realPtrToY = nil // or &Foo{} or whatever 

} 

Đây là an toàn hơn, vì nó sẽ luôn tìm ra tên trường đã chính xác, nhưng nó vẫn không thân thiện, có lẽ chậm, và tôi không chắc chắn nếu nó lộn xộn với bộ sưu tập rác. Nó cũng sẽ không cảnh báo bạn nếu bạn đang làm điều gì đó kỳ quặc (bạn có thể làm cho mã này trở thành một số nhỏ hơn an toàn hơn bằng cách thêm một vài kiểm tra, nhưng tôi sẽ không bận tâm, điều này có được ý chính cũng đủ).

Cũng xin lưu ý rằng FieldByName dễ bị nhà phát triển gói thay đổi tên của biến. Là một nhà phát triển gói, tôi có thể nói với bạn rằng tôi hoàn toàn không cảm thấy lo lắng về việc thay đổi tên của những thứ mà người dùng không biết. Bạn có thể sử dụng Field, nhưng sau đó bạn dễ bị nhà phát triển thay đổi thứ tự của các lĩnh vực mà không có cảnh báo, đó là một cái gì đó tôi cũng không có khiếu nại về làm. Hãy nhớ rằng sự kết hợp của phản ánh và không an toàn này là ... không an toàn, không giống như các thay đổi tên thông thường, điều này sẽ không cung cấp cho bạn một lỗi thời gian biên dịch. Thay vào đó, chương trình sẽ đột nhiên hoảng sợ hoặc làm điều gì đó kỳ lạ và không xác định bởi vì nó có trường sai, có nghĩa là ngay cả khi bạn là nhà phát triển gói đã thay đổi tên, bạn vẫn có thể không nhớ ở mọi nơi bạn đã thực hiện thủ thuật này và dành thời gian theo dõi lý do tại sao các thử nghiệm của bạn đột nhiên bị phá vỡ vì trình biên dịch không phàn nàn.Tôi đã đề cập rằng đây là một ý tưởng tồi?

Edit2: Kể từ khi bạn đề cập đến thử nghiệm White Box, lưu ý rằng nếu bạn đặt tên cho một tập tin trong thư mục của bạn <whatever>_test.go nó sẽ không biên dịch, trừ khi bạn sử dụng go test, vì vậy nếu bạn muốn làm thử nghiệm hộp màu trắng, ở đầu tuyên bố package <yourpackage> mà sẽ cung cấp cho bạn quyền truy cập vào các trường chưa được xuất bản và nếu bạn muốn thực hiện thử nghiệm hộp đen thì bạn sử dụng package <yourpackage>_test.

Nếu bạn cần hộp trắng kiểm tra hai gói cùng một lúc, tuy nhiên, tôi nghĩ bạn có thể bị kẹt và có thể cần phải suy nghĩ lại về thiết kế của bạn.

+0

Câu trả lời tuyệt vời và +1000 trên "[...] không có gì phải lo về việc thay đổi tên của những thứ mà người dùng sẽ không biết". Nó không được báo cáo vì một lý do. – mna

+1

Chỉ cần rõ ràng, tất cả các gói này là của riêng tôi, vì vậy nếu tôi thay đổi tên của một trường, tôi sẽ biết về nó. Tôi đã có hai cách sử dụng tiềm năng trong tâm trí: thử nghiệm hộp trắng, giải pháp của bạn chắc chắn hoạt động, nhưng cũng là một trình phân tích cú pháp, chuyển đổi chuỗi thành các đối tượng trong gói khác, hiệu quả của nó sẽ vượt qua các nhà xây dựng thông thường cho các cấu trúc. sẽ đánh bại mục đích của điều đó. Tất nhiên, tôi có thể đặt trình phân tích cú pháp trong cùng một gói, nhưng tôi muốn giữ các phần khác nhau của chương trình riêng biệt (có thể đó không phải là rất giống như vậy?). – Matt

+4

Kiểm tra hộp màu trắng có thể dễ dàng thực hiện bằng cách đặt các tệp * _test.go của bạn trong cùng một gói như gói đang được thử nghiệm, do đó bạn có quyền truy cập vào các trường chưa được xuất bản. Các công cụ Go chính xác hỗ trợ trường hợp sử dụng này và sẽ không biên dịch các thử nghiệm của bạn với mã gói của bạn trừ khi chạy 'go test'. – mna

Các vấn đề liên quan