Có cách nào để mô phỏng khái niệm 'người bạn' của C ++ trong Java không?


196

Tôi muốn có thể viết một lớp Java trong một gói có thể truy cập các phương thức không công khai của một lớp trong một gói khác mà không phải biến nó thành một lớp con của lớp khác. Điều này có thể không?

Câu trả lời:


466

Đây là một mẹo nhỏ mà tôi sử dụng trong JAVA để sao chép cơ chế bạn bè C ++.

Hãy nói rằng tôi có một lớp Romeovà một lớp khác Juliet. Họ ở trong các gói khác nhau (gia đình) vì lý do thù hận.

Romeomuốn cuddle Julietvà chỉ Julietmuốn cho Romeo cuddlecô ấy

Trong C ++, Julietsẽ tuyên bố Romeolà (người yêu) friendnhưng không có những thứ như vậy trong java.

Dưới đây là các lớp và mẹo:

Ưu tiên phái đẹp :

package capulet;

import montague.Romeo;

public class Juliet {

    public static void cuddle(Romeo.Love love) {
        Objects.requireNonNull(love);
        System.out.println("O Romeo, Romeo, wherefore art thou Romeo?");
    }

}

Vì vậy, phương pháp Juliet.cuddlepublicnhưng bạn cần phải Romeo.Lovegọi nó. Nó sử dụng điều này Romeo.Lovenhư một "bảo mật chữ ký" để đảm bảo rằng chỉ Romeocó thể gọi phương thức này và kiểm tra xem tình yêu là có thật để thời gian chạy sẽ thực hiện NullPointerExceptionnếu có null.

Bây giờ con trai:

package montague;

import capulet.Juliet;

public class Romeo {
    public static final class Love { private Love() {} }
    private static final Love love = new Love();

    public static void cuddleJuliet() {
        Juliet.cuddle(love);
    }
}

Lớp Romeo.Lovenày là công khai, nhưng hàm tạo của nó là private. Do đó, bất cứ ai cũng có thể nhìn thấy nó, nhưng chỉ Romeocó thể xây dựng nó. Tôi sử dụng một tham chiếu tĩnh để cái Romeo.Lovekhông bao giờ được sử dụng chỉ được xây dựng một lần và không ảnh hưởng đến tối ưu hóa.

Do đó, Romeocó thể cuddle Julietvà duy nhất anh có thể bởi vì chỉ có ông có thể xây dựng và tiếp cận một Romeo.Loveví dụ, đó là yêu cầu của Julietđể cuddlecô (hoặc nếu không cô ấy sẽ tát cho bạn một NullPointerException).


107
+1 cho "tát bạn với NullPulumException". Rất ấn tượng.
Nickolas

2
@Steazy Có: tìm các chú thích NotNull, NonNull và CheckForNull. Kiểm tra tài liệu IDE của bạn để tìm hiểu cách sử dụng và thi hành các chú thích đó. Tôi biết rằng IntelliJ nhúng điều này theo mặc định và nhật thực cần một plugin (chẳng hạn như FindBugs).
Salomon BRYS

27
Bạn có thể làm Romeo's Lovecho Juliađời đời bằng cách thay đổi các lovelĩnh vực được final;-).
Matthias

5
@Matthias Lĩnh vực tình yêu là tĩnh ... Tôi sẽ chỉnh sửa câu trả lời để đưa ra kết quả cuối cùng;)
Salomon BRYS

12
Tất cả các câu trả lời nên như thế này (Y) +1 cho ví dụ hài hước và tuyệt vời.
Zia Ul Rehman Mughal

54

Các nhà thiết kế của Java đã từ chối rõ ràng ý tưởng về người bạn khi nó hoạt động trong C ++. Bạn đặt "bạn bè" của bạn trong cùng một gói. Bảo mật riêng tư, được bảo vệ và đóng gói được thi hành như một phần của thiết kế ngôn ngữ.

James Gosling muốn Java trở thành C ++ mà không mắc lỗi. Tôi tin rằng anh ấy cảm thấy người bạn đó là một sai lầm vì nó vi phạm các nguyên tắc OOP. Các gói cung cấp một cách hợp lý để tổ chức các thành phần mà không quá thuần túy về OOP.

NR đã chỉ ra rằng bạn có thể gian lận bằng cách sử dụng sự phản chiếu, nhưng thậm chí nó chỉ hoạt động nếu bạn không sử dụng SecurityManager. Nếu bạn bật bảo mật tiêu chuẩn Java, bạn sẽ không thể gian lận với sự phản chiếu trừ khi bạn viết chính sách bảo mật để cho phép cụ thể.


11
Tôi không có nghĩa là một nhà giáo, nhưng sửa đổi truy cập không phải là một cơ chế bảo mật.
Greg D

6
Công cụ sửa đổi truy cập là một phần của mô hình bảo mật java. Tôi đã đặc biệt đề cập đến java.lang.R nbPermission để phản ánh: accessDeclaredMembers và accessClassInPackage.
David G

54
Nếu Gosling thực sự nghĩ rằng friendđã vi phạm OOP (đặc biệt, hơn cả truy cập gói) thì anh ta thực sự không hiểu điều đó (hoàn toàn có thể, nhiều người hiểu nhầm về nó).
Konrad Rudolph

8
Các thành phần lớp đôi khi cần được tách ra (ví dụ: triển khai và API, đối tượng cốt lõi và bộ điều hợp). Bảo vệ cấp gói đồng thời quá dễ dãi và quá hạn chế để thực hiện việc này đúng cách.
dhardy

2
@GregD Chúng có thể được coi là một cơ chế bảo mật theo nghĩa là chúng giúp ngăn chặn các nhà phát triển sử dụng một thành viên lớp không chính xác. Tôi nghĩ rằng họ có lẽ tốt hơn nên được gọi là một cơ chế an toàn .
nghiền nát

45

Ví dụ, khái niệm 'người bạn' rất hữu ích trong Java để tách API khỏi việc triển khai. Thông thường các lớp triển khai cần có quyền truy cập vào các lớp bên trong API nhưng chúng không được tiếp xúc với các máy khách API. Điều này có thể đạt được bằng cách sử dụng mẫu 'Trình truy cập bạn bè' như chi tiết bên dưới:

Lớp tiếp xúc thông qua API:

package api;

public final class Exposed {
    static {
        // Declare classes in the implementation package as 'friends'
        Accessor.setInstance(new AccessorImpl());
    }

    // Only accessible by 'friend' classes.
    Exposed() {

    }

    // Only accessible by 'friend' classes.
    void sayHello() {
        System.out.println("Hello");
    }

    static final class AccessorImpl extends Accessor {
        protected Exposed createExposed() {
            return new Exposed();
        }

        protected void sayHello(Exposed exposed) {
            exposed.sayHello();
        }
    }
}

Lớp cung cấp chức năng 'bạn bè':

package impl;

public abstract class Accessor {

    private static Accessor instance;

    static Accessor getInstance() {
        Accessor a = instance;
        if (a != null) {
            return a;
        }

        return createInstance();
    }

    private static Accessor createInstance() {
        try {
            Class.forName(Exposed.class.getName(), true, 
                Exposed.class.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }

        return instance;
    }

    public static void setInstance(Accessor accessor) {
        if (instance != null) {
            throw new IllegalStateException(
                "Accessor instance already set");
        }

        instance = accessor;
    }

    protected abstract Exposed createExposed();

    protected abstract void sayHello(Exposed exposed);
}

Ví dụ truy cập từ một lớp trong gói triển khai 'bạn bè':

package impl;

public final class FriendlyAccessExample {
    public static void main(String[] args) {
        Accessor accessor = Accessor.getInstance();
        Exposed exposed = accessor.createExposed();
        accessor.sayHello(exposed);
    }
}

1
Bởi vì tôi không biết "tĩnh" nghĩa là gì trong lớp "Exposed": Khối tĩnh, là một khối câu lệnh bên trong một lớp Java sẽ được thực thi khi một lớp được tải lần đầu vào JVM Đọc thêm tại javatutorialhub. com / Bắn
Guy L

Mẫu thú vị nhưng nó yêu cầu các lớp Exposed và Accessor phải công khai trong khi các lớp triển khai API (nghĩa là một tập hợp các lớp Java thực hiện một bộ giao diện Java công cộng) sẽ tốt hơn là "được bảo vệ mặc định" và do đó, không thể truy cập được vào máy khách để tách các loại từ việc thực hiện của họ.
Yann-Gaël Guéhéneuc

8
Tôi khá thô lỗ với Java của mình, vì vậy hãy tha thứ cho sự thiếu hiểu biết của tôi. Ưu điểm của việc này so với giải pháp "Romeo và Juliet" mà Salomon BRYS đã đăng là gì? Việc thực hiện này sẽ khiến tôi sợ hãi nếu tôi vấp phải nó trong một cơ sở mã (không có lời giải thích của bạn kèm theo, tức là bình luận nặng nề). Cách tiếp cận Romeo và Juliet rất đơn giản để hiểu.
Steazy

1
Cách tiếp cận này sẽ làm cho các vấn đề chỉ hiển thị khi chạy, trong khi việc sử dụng sai mục đích của Romeo và Juliet sẽ khiến chúng hiển thị vào thời gian biên dịch, trong khi đang phát triển.
ymajoros

1
@ymajoros Ví dụ Romeo và Juliet không hiển thị sai mục đích tại thời gian biên dịch. Nó dựa vào một đối số được truyền chính xác và một ngoại lệ được đưa ra. Đó là cả hai hành động thời gian.
Radiodef

10

Có hai giải pháp cho câu hỏi của bạn không liên quan đến việc giữ tất cả các lớp trong cùng một gói.

Đầu tiên là sử dụng mẫu Gói truy cập bạn bè / Gói bạn bè được mô tả trong (Thiết kế API thực tế, Tulach 2008).

Thứ hai là sử dụng OSGi. Có một bài viết ở đây giải thích cách OSGi thực hiện điều này.

Câu hỏi liên quan: 1 , 23 .


7

Theo tôi biết, điều đó là không thể.

Có lẽ, bạn có thể cung cấp cho chúng tôi thêm một số chi tiết về thiết kế của bạn. Những câu hỏi như thế này có khả năng là kết quả của lỗi thiết kế.

Chỉ cần xem xét

  • Tại sao các lớp trong các gói khác nhau, nếu chúng có liên quan chặt chẽ với nhau?
  • A có truy cập các thành viên tư nhân của B hay hoạt động nên được chuyển sang lớp B và được kích hoạt bởi A?
  • Đây thực sự là cuộc gọi hay xử lý sự kiện tốt hơn?

3

câu trả lời của eirikma rất dễ dàng và xuất sắc. Tôi có thể thêm một điều nữa: thay vì có một phương thức có thể truy cập công khai, getFriend () để có được một người bạn không thể sử dụng, bạn có thể tiến thêm một bước và không cho phép kết bạn mà không có mã thông báo: getFriend (Service.FriendToken). FriendToken này sẽ là một lớp công khai bên trong với một nhà xây dựng riêng, do đó chỉ có Dịch vụ mới có thể khởi tạo một lớp.


3

Đây là một ví dụ trường hợp sử dụng rõ ràng với một Friendlớp có thể tái sử dụng . Lợi ích của cơ chế này là sử dụng đơn giản. Có thể tốt cho việc cung cấp các lớp kiểm tra đơn vị truy cập nhiều hơn so với phần còn lại của ứng dụng.

Để bắt đầu, đây là một ví dụ về cách sử dụng Friendlớp.

public class Owner {
    private final String member = "value";

    public String getMember(final Friend friend) {
        // Make sure only a friend is accepted.
        friend.is(Other.class);
        return member;
    }
}

Sau đó, trong gói khác, bạn có thể làm điều này:

public class Other {
    private final Friend friend = new Friend(this);

    public void test() {
        String s = new Owner().getMember(friend);
        System.out.println(s);
    }
}

Các Friendlớp học là như sau.

public final class Friend {
    private final Class as;

    public Friend(final Object is) {
        as = is.getClass();
    }

    public void is(final Class c) {
        if (c == as)
            return;
        throw new ClassCastException(String.format("%s is not an expected friend.", as.getName()));
    }

    public void is(final Class... classes) {
        for (final Class c : classes)
            if (c == as)
                return;
        is((Class)null);
    }
}

Tuy nhiên, vấn đề là nó có thể bị lạm dụng như vậy:

public class Abuser {
    public void doBadThings() {
        Friend badFriend = new Friend(new Other());
        String s = new Owner().getMember(badFriend);
        System.out.println(s);
    }
}

Bây giờ, có thể đúng là Otherlớp không có bất kỳ hàm tạo công khai nào, do đó làm cho Abusermã trên không thể thực hiện được. Tuy nhiên, nếu lớp học của bạn không có một constructor nào thì nó có lẽ là nên lặp lại trong các lớp bạn bè như một lớp bên trong. Lấy Other2lớp này làm ví dụ:

public class Other2 {
    private final Friend friend = new Friend();

    public final class Friend {
        private Friend() {}
        public void check() {}
    }

    public void test() {
        String s = new Owner2().getMember(friend);
        System.out.println(s);
    }
}

Và sau đó Owner2lớp học sẽ như thế này:

public class Owner2 {
    private final String member = "value";

    public String getMember(final Other2.Friend friend) {
        friend.check();
        return member;
    }
}

Lưu ý rằng Other2.Friendlớp có một hàm tạo riêng, do đó làm cho cách này an toàn hơn nhiều.


2

Các giải pháp được cung cấp có lẽ không phải là đơn giản nhất. Một cách tiếp cận khác dựa trên ý tưởng tương tự như trong C ++: các thành viên riêng không thể truy cập được ngoài phạm vi gói / riêng, ngoại trừ một lớp cụ thể mà chủ sở hữu tự kết bạn.

Lớp cần bạn bè truy cập vào một thành viên sẽ tạo ra một "lớp bạn" trừu tượng công khai bên trong mà lớp sở hữu các thuộc tính ẩn có thể xuất quyền truy cập, bằng cách trả về một lớp con thực hiện các phương thức thực hiện truy cập. Phương thức "API" của lớp bạn bè có thể là riêng tư nên không thể truy cập được bên ngoài lớp cần truy cập bạn bè. Tuyên bố duy nhất của nó là một cuộc gọi đến một thành viên được bảo vệ trừu tượng mà lớp xuất khẩu thực hiện.

Đây là mã:

Đầu tiên thử nghiệm xác minh rằng điều này thực sự hoạt động:

package application;

import application.entity.Entity;
import application.service.Service;
import junit.framework.TestCase;

public class EntityFriendTest extends TestCase {
    public void testFriendsAreOkay() {
        Entity entity = new Entity();
        Service service = new Service();
        assertNull("entity should not be processed yet", entity.getPublicData());
        service.processEntity(entity);
        assertNotNull("entity should be processed now", entity.getPublicData());
    }
}

Sau đó, Dịch vụ cần bạn bè truy cập vào gói thành viên riêng của Thực thể:

package application.service;

import application.entity.Entity;

public class Service {

    public void processEntity(Entity entity) {
        String value = entity.getFriend().getEntityPackagePrivateData();
        entity.setPublicData(value);
    }

    /**
     * Class that Entity explicitly can expose private aspects to subclasses of.
     * Public, so the class itself is visible in Entity's package.
     */
    public static abstract class EntityFriend {
        /**
         * Access method: private not visible (a.k.a 'friendly') outside enclosing class.
         */
        private String getEntityPackagePrivateData() {
            return getEntityPackagePrivateDataImpl();
        }

        /** contribute access to private member by implementing this */
        protected abstract String getEntityPackagePrivateDataImpl();
    }
}

Cuối cùng: lớp Thực thể cung cấp quyền truy cập thân thiện vào một thành viên riêng của gói chỉ đến lớp application.service.Service.

package application.entity;

import application.service.Service;

public class Entity {

    private String publicData;
    private String packagePrivateData = "secret";   

    public String getPublicData() {
        return publicData;
    }

    public void setPublicData(String publicData) {
        this.publicData = publicData;
    }

    String getPackagePrivateData() {
        return packagePrivateData;
    }

    /** provide access to proteced method for Service'e helper class */
    public Service.EntityFriend getFriend() {
        return new Service.EntityFriend() {
            protected String getEntityPackagePrivateDataImpl() {
                return getPackagePrivateData();
            }
        };
    }
}

Được rồi, tôi phải thừa nhận nó dài hơn một chút so với "dịch vụ bạn bè :: Dịch vụ;" nhưng có thể rút ngắn nó trong khi vẫn kiểm tra thời gian biên dịch bằng cách sử dụng các chú thích.


Điều này không hoàn toàn hoạt động như một lớp bình thường trong cùng một gói có thể chỉ lấy getFriend () và sau đó gọi phương thức được bảo vệ bỏ qua lớp riêng.
dùng2219808

1

Trong Java có thể có "sự thân thiện liên quan đến gói". Điều này có thể được sử dụng để thử nghiệm đơn vị. Nếu bạn không chỉ định riêng tư / công khai / được bảo vệ trước một phương thức, nó sẽ là "bạn trong gói". Một lớp trong cùng một gói sẽ có thể truy cập nó, nhưng nó sẽ ở chế độ riêng tư bên ngoài lớp.

Quy tắc này không phải lúc nào cũng được biết đến và đó là một xấp xỉ tốt của từ khóa "bạn bè" C ++. Tôi thấy nó là một sự thay thế tốt.


1
Điều này là đúng, nhưng tôi đã thực sự hỏi về mã nằm trong các gói khác nhau ...
Matthew Murdoch

1

Tôi nghĩ rằng các lớp bạn bè trong C ++ giống như khái niệm lớp bên trong trong Java. Sử dụng các lớp bên trong, bạn thực sự có thể định nghĩa một lớp kèm theo và một lớp kèm theo. Lớp kèm theo có toàn quyền truy cập vào các thành viên công cộng và tư nhân của lớp kèm theo. xem liên kết sau: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html


Er, không, họ không. Nó giống như tình bạn hơn trong cuộc sống thực: Nó có thể, nhưng không cần phải là, lẫn nhau (A là bạn của B không có nghĩa là B được coi là bạn của A), và bạn và bạn bè của bạn có thể hoàn toàn khác nhau gia đình và có những vòng tròn bạn bè của riêng bạn, có thể (nhưng không nhất thiết). (Không phải tôi muốn xem các lớp học với nhiều bạn bè. Nó có thể là một tính năng hữu ích, nhưng nên thận trọng khi sử dụng.)
Christopher Creutzig

1

Tôi nghĩ rằng, cách tiếp cận sử dụng mô hình người truy cập bạn bè là quá phức tạp. Tôi đã phải đối mặt với cùng một vấn đề và tôi đã giải quyết bằng cách sử dụng hàm tạo sao chép cũ, tốt, được biết đến từ C ++, trong Java:

public class ProtectedContainer {
    protected String iwantAccess;

    protected ProtectedContainer() {
        super();
        iwantAccess = "Default string";
    }

    protected ProtectedContainer(ProtectedContainer other) {
        super();
        this.iwantAccess = other.iwantAccess;
    }

    public int calcSquare(int x) {
        iwantAccess = "calculated square";
        return x * x;
    }
}

Trong ứng dụng của bạn, bạn có thể viết mã sau đây:

public class MyApp {

    private static class ProtectedAccessor extends ProtectedContainer {

        protected ProtectedAccessor() {
            super();
        }

        protected PrivateAccessor(ProtectedContainer prot) {
            super(prot);
        }

        public String exposeProtected() {
            return iwantAccess;
        }
    }
}

Ưu điểm của phương pháp này là chỉ ứng dụng của bạn có quyền truy cập vào dữ liệu được bảo vệ. Nó không chính xác là một sự thay thế của từ khóa bạn bè. Nhưng tôi nghĩ nó khá phù hợp khi bạn viết thư viện tùy chỉnh và bạn cần truy cập dữ liệu được bảo vệ.

Bất cứ khi nào bạn phải đối phó với các phiên bản của ProtectedContainer, bạn có thể bọc ProtectedAccessor của mình xung quanh nó và bạn có quyền truy cập.

Nó cũng hoạt động với các phương pháp được bảo vệ. Bạn xác định chúng được bảo vệ trong API của bạn. Sau đó trong ứng dụng của bạn, bạn viết một lớp bao bọc riêng và hiển thị phương thức được bảo vệ là công khai. Đó là nó.


1
Nhưng ProtectedContainercó thể được phân lớp bên ngoài gói!
Raphael

0

Nếu bạn muốn truy cập các phương thức được bảo vệ, bạn có thể tạo một lớp con của lớp bạn muốn sử dụng để hiển thị các phương thức bạn muốn sử dụng dưới dạng công khai (hoặc bên trong không gian tên để an toàn hơn) và có một thể hiện của lớp đó trong lớp của bạn (sử dụng nó như một proxy).

Theo như các phương pháp riêng tư có liên quan (tôi nghĩ) bạn đã hết may mắn.


0

Tôi đồng ý rằng trong hầu hết các trường hợp, từ khóa bạn bè là không cần thiết.

  • Gói-private (hay còn gọi là mặc định) là đủ trong hầu hết các trường hợp khi bạn có một nhóm các lớp đan xen rất nhiều
  • Đối với các lớp gỡ lỗi muốn truy cập vào bên trong, tôi thường đặt phương thức riêng tư và truy cập nó thông qua sự phản chiếu. Tốc độ thường không quan trọng ở đây
  • Đôi khi, bạn thực hiện một phương pháp là "hack" hoặc nếu không thì có thể thay đổi. Tôi công khai nó, nhưng sử dụng @Deprecated để chỉ ra rằng bạn không nên dựa vào phương pháp này hiện có.

Và cuối cùng, nếu nó thực sự cần thiết, có mẫu người truy cập bạn bè được đề cập trong các câu trả lời khác.


0

Không sử dụng một từ khóa hoặc như vậy.

Bạn có thể "gian lận" bằng cách sử dụng sự phản chiếu, v.v., nhưng tôi không khuyên bạn nên "gian lận".


3
Tôi sẽ coi đây là một ý tưởng tồi tệ đến mức thậm chí còn cho rằng nó thật đáng ghê tởm đối với tôi. Rõ ràng đây là một mối quan hệ tốt nhất, và không nên là một phần của bất kỳ thiết kế nào.
shsteimer

0

Một phương pháp tôi đã tìm thấy để giải quyết vấn đề này là tạo một đối tượng truy cập, như vậy:

class Foo {
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* This is the accessor. Anyone with a reference to this has special access. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    /** You get an accessor by calling this method. This method can only
     * be called once, so calling is like claiming ownership of the accessor. */
    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }
}

Mã đầu tiên để gọi getAccessor()"quyền sở hữu" của người truy cập. Thông thường, đây là mã tạo ra đối tượng.

Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.

Điều này cũng có một lợi thế so với cơ chế bạn bè của C ++, vì nó cho phép bạn giới hạn quyền truy cập ở cấp độ cá thể , trái ngược với cấp độ mỗi lớp . Bằng cách kiểm soát tham chiếu của người truy cập, bạn kiểm soát quyền truy cập vào đối tượng. Bạn cũng có thể tạo nhiều bộ truy cập và cung cấp quyền truy cập khác nhau cho mỗi bộ truy cập, cho phép kiểm soát chi tiết về mã nào có thể truy cập những gì:

class Foo {
    private String secret;
    private String locked;

    /* Anyone can get locked. */
    public String getLocked() { return locked; }

    /* Normal accessor. Can write to locked, but not read secret. */
    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    private FooAccessor accessor;

    public FooAccessor getAccessor() {
        if (accessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return accessor = new FooAccessor();
    }

    /* Super accessor. Allows access to secret. */
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    private FooSuperAccessor superAccessor;

    public FooSuperAccessor getAccessor() {
        if (superAccessor != null)
            throw new IllegalStateException("Cannot return accessor more than once!");
        return superAccessor = new FooSuperAccessor();
    }
}

Cuối cùng, nếu bạn muốn mọi thứ ngăn nắp hơn một chút, bạn có thể tạo một đối tượng tham chiếu, giữ mọi thứ lại với nhau. Điều này cho phép bạn yêu cầu tất cả các bộ truy cập với một cuộc gọi phương thức, cũng như giữ chúng cùng với thể hiện được liên kết của chúng. Khi bạn có tài liệu tham khảo, bạn có thể chuyển những người truy cập ra mã cần nó:

class Foo {
    private String secret;
    private String locked;

    public String getLocked() { return locked; }

    public class FooAccessor {
        private FooAccessor (){};
        public void setLocked(String locked) { Foo.this.locked = locked; }
    }
    public class FooSuperAccessor {
        private FooSuperAccessor (){};
        public String getSecret() { return Foo.this.secret; }
    }
    public class FooReference {
        public final Foo foo;
        public final FooAccessor accessor;
        public final FooSuperAccessor superAccessor;

        private FooReference() {
            this.foo = Foo.this;
            this.accessor = new FooAccessor();
            this.superAccessor = new FooSuperAccessor();
        }
    }

    private FooReference reference;

    /* Beware, anyone with this object has *all* the accessors! */
    public FooReference getReference() {
        if (reference != null)
            throw new IllegalStateException("Cannot return reference more than once!");
        return reference = new FooReference();
    }
}

Sau nhiều lần đập đầu (không phải loại tốt), đây là giải pháp cuối cùng của tôi và tôi rất thích nó. Nó linh hoạt, đơn giản để sử dụng và cho phép kiểm soát truy cập lớp rất tốt. (Quyền truy cập chỉ tham chiếu là rất hữu ích.) Nếu bạn sử dụng được bảo vệ thay vì riêng tư cho người truy cập / tài liệu tham khảo, các lớp con của Foo thậm chí có thể trả về các tham chiếu mở rộng từ đó getReference. Nó cũng không yêu cầu bất kỳ sự phản chiếu nào, vì vậy nó có thể được sử dụng trong mọi môi trường.


0

Kể từ Java 9, các mô-đun có thể được sử dụng để làm cho vấn đề này không thành vấn đề trong nhiều trường hợp.


0

Tôi thích ủy quyền hoặc thành phần hoặc lớp nhà máy (tùy thuộc vào vấn đề dẫn đến vấn đề này) để tránh biến nó thành một lớp công khai.

Nếu đó là vấn đề "lớp giao diện / lớp triển khai trong các gói khác nhau", thì tôi sẽ sử dụng lớp nhà máy công cộng có cùng gói với gói impl và ngăn chặn sự phơi bày của lớp impl.

Nếu đó là vấn đề "Tôi ghét phải công khai lớp / phương thức này chỉ để cung cấp chức năng này cho một số lớp khác trong một gói khác", thì tôi sẽ sử dụng một lớp đại biểu công khai trong cùng một gói và chỉ hiển thị một phần chức năng đó cần thiết bởi lớp "người ngoài".

Một số trong các quyết định này được điều khiển bởi kiến ​​trúc tải lớp máy chủ đích (gói OSGi, WAR / EAR, v.v.), các quy ước đặt tên gói và triển khai. Ví dụ: giải pháp được đề xuất ở trên, mẫu 'Trình truy cập bạn bè' rất thông minh cho các ứng dụng java thông thường. Tôi tự hỏi nếu nó trở nên khó khăn để thực hiện nó trong OSGi do sự khác biệt trong phong cách nạp lớp.


0

Tôi không biết nó có hữu dụng với ai không nhưng tôi đã xử lý nó theo cách sau:

Tôi đã tạo một giao diện (AdminRights).

Mỗi lớp có thể gọi các hàm đã nói sẽ triển khai AdminRights.

Sau đó, tôi đã tạo một hàm HasAdminRights như sau:

private static final boolean HasAdminRights()
{
    // Gets the current hierarchy of callers
    StackTraceElement[] Callers = new Throwable().getStackTrace();

    // Should never occur with me but if there are less then three StackTraceElements we can't check
    if (Callers.length < 3)
    {
        EE.InvalidCode("Couldn't check for administrator rights");
        return false;

    } else try
    {

        // Now we check the third element as this function is the first and the function wanting to check for the rights the second. We try to use it as a subclass of AdminRights.
        Class.forName(Callers[2].getClassName()).asSubclass(AdminRights.class);

        // If everything worked up to now, it has admin rights!
        return true;

    } catch (java.lang.ClassCastException | ClassNotFoundException e)
    {
        // In the catch, something went wrong and we can deduce that the caller has no admin rights

        EE.InvalidCode(Callers[1].getClassName() + " doesn't have administrator rights");
        return false;
    }
}

-1

Tôi đã từng thấy một giải pháp dựa trên sự phản chiếu đã "kiểm tra bạn bè" trong thời gian chạy bằng cách sử dụng sự phản chiếu và kiểm tra ngăn xếp cuộc gọi để xem liệu lớp gọi phương thức đó có được phép làm như vậy không. Là một kiểm tra thời gian chạy, nó có nhược điểm rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.