มีวิธีจำลองแนวคิด 'เพื่อน' ของ C ++ ใน Java หรือไม่?


197

ฉันต้องการที่จะสามารถเขียนคลาส Java ในแพ็คเกจหนึ่งซึ่งสามารถเข้าถึงวิธีการที่ไม่ใช่แบบสาธารณะของคลาสในแพ็กเกจอื่นโดยไม่ต้องทำให้คลาสย่อยของคลาสอื่น เป็นไปได้ไหม

คำตอบ:


468

นี่เป็นเคล็ดลับเล็ก ๆ ที่ฉันใช้ใน JAVA เพื่อทำซ้ำกลไกเพื่อน C ++

ให้บอกว่าฉันมีชั้นเรียนRomeoและชั้นเรียนอื่นJulietและชั้นอื่นพวกเขาอยู่ในแพ็คเกจที่แตกต่างกัน (ครอบครัว) ด้วยเหตุผลความเกลียดชัง

Romeoต้องการcuddle JulietและJulietต้องการให้เท่านั้นRomeo cuddleเธอเท่านั้น

ใน C ++ JulietจะประกาศRomeoเป็น (คนรัก) friendแต่ไม่มีสิ่งเช่นนี้ใน java

นี่คือคลาสและเคล็ดลับ:

สุภาพสตรีก่อน:

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?");
    }

}

ดังนั้นวิธีการJuliet.cuddleคือpublicแต่คุณต้องRomeo.Loveเรียกมัน มันใช้นี้Romeo.Loveเป็น "ลายเซ็นการรักษาความปลอดภัย" เพื่อให้แน่ใจว่าRomeoสามารถเรียกวิธีนี้และตรวจสอบว่าความรักที่เป็นจริงเพื่อให้รันไทม์จะโยนถ้ามันเป็นNullPointerExceptionnull

ตอนนี้เด็ก ๆ :

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);
    }
}

ชั้นRomeo.Loveเป็นที่สาธารณะ privateแต่คอนสตรัคของมันคือ ดังนั้นทุกคนสามารถเห็นได้ แต่Romeoสามารถสร้างได้ ฉันใช้การอ้างอิงแบบคงที่ดังนั้นสิ่งRomeo.Loveที่ไม่เคยใช้จะถูกสร้างเพียงครั้งเดียวและไม่ส่งผลกระทบต่อการปรับให้เหมาะสม

ดังนั้นRomeoสามารถcuddle Julietและมีเพียงเขาสามารถเพราะเพียง แต่เขาสามารถสร้างและเข้าถึงRomeo.Loveอินสแตนซ์ที่ถูกต้องตามJulietเพื่อcuddleเธอ (หรืออื่น ๆ ที่เธอจะตบคุณด้วยNullPointerException)


107
+1 สำหรับ "ตบคุณด้วย NullPointerException" ที่น่าประทับใจมาก.
ลัส

2
@Steazy There คือ: มองหาคำอธิบายประกอบ NotNull, NonNull และ CheckForNull ตรวจสอบกับเอกสารประกอบของ IDE ของคุณเพื่อค้นหาวิธีใช้และบังคับใช้คำอธิบายประกอบเหล่านั้น ฉันรู้ว่า IntelliJ ฝังสิ่งนี้ตามค่าเริ่มต้นและ eclipse นั้นต้องการปลั๊กอิน (เช่น FindBugs)
ซาโลมอน BRYS

27
คุณสามารถทำให้Romeo's LoveสำหรับJuliaนิรันดร์โดยการเปลี่ยนloveข้อมูลให้เป็นfinal;-)
Matthias

5
@Matthias สนามรักคงที่ ... ฉันจะแก้ไขคำตอบเพื่อให้เป็นที่สิ้นสุด;)
ซาโลมอน BRYS

12
คำตอบทั้งหมดควรเป็นเช่นนี้ (Y) +1 สำหรับเรื่องตลกและแบบอย่างที่ดี
เซียอูลเรห์มันโมกุล

54

นักออกแบบของ Java ปฏิเสธแนวคิดของเพื่อนอย่างชัดเจนเนื่องจากทำงานใน C ++ คุณใส่ "เพื่อน" ของคุณในแพ็คเกจเดียวกัน ความปลอดภัยส่วนบุคคลที่ได้รับการป้องกันและจัดแพคเกจนั้นมีการบังคับใช้เป็นส่วนหนึ่งของการออกแบบภาษา

James Gosling ต้องการให้ Java เป็น C ++ โดยไม่มีข้อผิดพลาด ฉันเชื่อว่าเขารู้สึกว่าเพื่อนคนนั้นเป็นความผิดพลาดเพราะมันละเมิดหลักการของ OOP แพคเกจให้วิธีการที่เหมาะสมในการจัดระเบียบส่วนประกอบโดยไม่พิถีพิถันเกินไปเกี่ยวกับ OOP

NR ชี้ให้เห็นว่าคุณสามารถโกงโดยใช้การสะท้อนได้ แต่แม้จะใช้งานได้ก็ต่อเมื่อคุณไม่ได้ใช้ SecurityManager หากคุณเปิดการรักษาความปลอดภัยมาตรฐานของ Java คุณจะไม่สามารถโกงการสะท้อนกลับได้เว้นแต่คุณจะเขียนนโยบายความปลอดภัยเพื่อให้อนุญาตเป็นการเฉพาะ


11
ฉันไม่ได้ตั้งใจจะเป็นคนอวดรู้ แต่ตัวดัดแปลงการเข้าถึงไม่ใช่กลไกความปลอดภัย
Greg D

6
ตัวดัดแปลงการเข้าถึงเป็นส่วนหนึ่งของโมเดลความปลอดภัยของจาวา ฉันอ้างถึง java.lang.RuntimePermission โดยเฉพาะสำหรับการสะท้อน: accessDeclaredMembers และ accessClassInPackage
David G

54
ถ้า Gosling คิดว่าfriendเป็นการละเมิด OOP (โดยเฉพาะมากกว่าการเข้าถึงแพ็คเกจ) เขาก็ไม่เข้าใจจริงๆ (เป็นไปได้ทั้งหมดมีหลายคนเข้าใจผิด)
Konrad Rudolph

8
บางครั้งต้องแยกส่วนประกอบของคลาส (เช่นการนำไปใช้และ API, วัตถุหลักและอะแดปเตอร์) การป้องกันระดับแพ็คเกจเป็นไปในเวลาเดียวกันอนุญาตและ จำกัด เกินไปที่จะทำสิ่งนี้อย่างถูกต้อง
dhardy

2
@GregD พวกเขาอาจถูกมองว่าเป็นกลไกความปลอดภัยในแง่ที่ว่าพวกเขาช่วยป้องกันนักพัฒนาจากการใช้สมาชิกคลาสอย่างไม่ถูกต้อง ฉันคิดว่าพวกเขาน่าจะถูกเรียกว่ากลไกความปลอดภัยที่ดีกว่า
บดขยี้

45

แนวคิด 'เพื่อน' มีประโยชน์ใน Java เช่นเพื่อแยก API ออกจากการใช้งาน เป็นเรื่องปกติที่คลาสการปรับใช้จะต้องเข้าถึง API ภายในคลาส แต่สิ่งเหล่านี้ไม่ควรสัมผัสกับไคลเอนต์ API สามารถทำได้โดยใช้รูปแบบ 'Friend Accessor' ดังรายละเอียดด้านล่าง:

คลาสที่เปิดเผยผ่าน 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();
        }
    }
}

ชั้นเรียนที่มีฟังก์ชั่น 'เพื่อน':

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);
}

ตัวอย่างการเข้าถึงจากชั้นเรียนในแพ็คเกจการใช้งาน 'เพื่อน':

package impl;

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

1
เพราะฉันไม่รู้ว่า "static" หมายถึงอะไรในคลาส "Exposed": บล็อกแบบสแตติกเป็นบล็อกของคำสั่งภายในคลาส Java ที่จะถูกดำเนินการเมื่อคลาสถูกโหลดเข้าสู่ JVM เป็นครั้งแรกอ่านเพิ่มเติมได้ที่javatutorialhub com / …
Guy L

รูปแบบที่น่าสนใจ แต่ต้องการให้คลาส Exposed และ Accessor เป็นสาธารณะในขณะที่คลาสที่ใช้ API (เช่นชุดของคลาส Java ที่ใช้ชุดของส่วนต่อประสาน Java สาธารณะ) จะดีกว่า "default default" จึงไม่สามารถเข้าถึงไคลเอ็นต์ได้ เพื่อแยกประเภทจากการใช้งานของพวกเขา
Yann-GaëlGuéhéneuc

8
ฉันค่อนข้างเป็นสนิมบน Java ของฉันดังนั้นให้อภัยความไม่รู้ของฉัน อะไรคือข้อได้เปรียบของเรื่องนี้ในเรื่อง "Romeo and Juliet" Salomon BRYS ที่โพสต์ การใช้งานนี้จะทำให้กางเกงแตกออกจากตัวฉันถ้าฉันสะดุดในฐานรหัส (ไม่มีคำอธิบายของคุณแนบมาเช่น วิธีการแบบโรมิโอและจูเลียตนั้นง่ายมากที่จะเข้าใจ
Steazy

1
วิธีการนี้จะทำให้มองเห็นปัญหาเฉพาะที่รันไทม์ในขณะที่การใช้ผิดวิธีของโรมิโอและจูเลียตจะทำให้พวกเขามองเห็นได้ในเวลารวบรวมขณะที่กำลังพัฒนา
ymajoros

1
@ymajoros ตัวอย่าง Romeo and Juliet ไม่สามารถมองเห็นในทางที่ผิดได้ในเวลารวบรวม มันอาศัยการโต้แย้งที่ถูกส่งผ่านอย่างถูกต้องและมีข้อยกเว้นที่ถูกโยน สิ่งเหล่านี้เป็นการกระทำในเวลาทำงาน
Radiodef

10

คำถามของคุณมีสองทางที่ไม่เกี่ยวข้องกับการรักษาคลาสทั้งหมดไว้ในแพ็คเกจเดียวกัน

สิ่งแรกคือการใช้รูปแบบแพคเกจเพื่อน Accessor / เพื่อนอธิบายไว้ใน (การออกแบบ API ปฏิบัติ, Tulach 2008)

ประการที่สองคือการใช้ OSGi มีบทความที่นี่อธิบายถึงวิธีการที่ OSGi ประสบความสำเร็จ

คำถามที่เกี่ยวข้อง: 1 , 2และ3


7

เท่าที่ฉันรู้มันเป็นไปไม่ได้

บางทีคุณอาจให้รายละเอียดเพิ่มเติมเกี่ยวกับการออกแบบของคุณ คำถามเช่นนี้น่าจะเป็นผลมาจากข้อบกพร่องในการออกแบบ

เพียงแค่พิจารณา

  • ทำไมคลาสเหล่านั้นในแพ็คเกจที่แตกต่างกันถ้ามันเกี่ยวข้องกันมาก
  • มี A เพื่อเข้าถึงสมาชิกส่วนตัวของ B หรือควรย้ายการดำเนินการไปที่คลาส B และถูกทริกเกอร์โดย A หรือไม่
  • สิ่งนี้เรียกจริง ๆ หรือจัดการเหตุการณ์ได้ดีขึ้นหรือไม่?

3

คำตอบของ eirikma นั้นง่ายและยอดเยี่ยม ฉันอาจเพิ่มอีกหนึ่งสิ่ง: แทนที่จะมีวิธีการเข้าถึงแบบสาธารณะ getFriend () เพื่อรับเพื่อนที่ไม่สามารถใช้งานได้คุณสามารถไปอีกขั้นหนึ่งแล้วไม่อนุญาตให้เพื่อนไม่มีโทเค็น: getFriend (Service.FriendToken) FriendToken นี้จะเป็นชั้นเรียนสาธารณะที่มีคอนสตรัคเตอร์ส่วนตัวเพื่อให้บริการเท่านั้นที่สามารถยกตัวอย่างได้


3

นี่คือตัวอย่างกรณีการใช้งานที่ชัดเจนพร้อมFriendคลาสที่ใช้ซ้ำได้ ประโยชน์ของกลไกนี้คือใช้งานง่าย อาจดีสำหรับการให้คลาสทดสอบหน่วยเข้าถึงมากกว่าส่วนที่เหลือของแอปพลิเคชัน

เพื่อเริ่มต้นนี่คือตัวอย่างวิธีการใช้Friendคลาส

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;
    }
}

จากนั้นในแพ็คเกจอื่นคุณสามารถทำได้:

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

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

Friendระดับมีดังนี้

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);
    }
}

อย่างไรก็ตามปัญหาคือมันสามารถถูกทารุณกรรมเช่น:

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

ตอนนี้มันอาจเป็นความจริงที่ว่าOtherคลาสไม่มีตัวสร้างสาธารณะใด ๆ ดังนั้นจึงไม่สามารถสร้างAbuserโค้ดด้านบนได้ แต่ถ้าระดับของคุณไม่ได้สร้างสาธารณะแล้วมันอาจจะแนะนำให้ซ้ำชั้นเพื่อนเป็นระดับชั้น นำOther2คลาสนี้มาเป็นตัวอย่าง:

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);
    }
}

แล้วOwner2ชั้นจะเป็นอย่างนี้:

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

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

ขอให้สังเกตว่าOther2.Friendชั้นเรียนมีคอนสตรัคเตอร์ส่วนตัวจึงทำให้วิธีนี้ปลอดภัยยิ่งขึ้น


2

โซลูชันที่ให้มาอาจไม่ใช่วิธีที่ง่ายที่สุด วิธีการอื่นจะขึ้นอยู่กับแนวคิดเดียวกันกับใน C ++: สมาชิกส่วนบุคคลไม่สามารถเข้าถึงได้นอกขอบเขตของแพคเกจ / ส่วนตัวยกเว้นสำหรับคลาสเฉพาะที่เจ้าของเป็นเพื่อนของตัวเอง

คลาสที่ต้องการการเข้าถึงเพื่อนกับสมาชิกควรสร้าง "class class" แบบนามธรรมสาธารณะที่คลาสที่เป็นเจ้าของคุณสมบัติที่ซ่อนอยู่สามารถส่งออกการเข้าถึงโดยส่งคืนคลาสย่อยที่ใช้วิธีการใช้งานแบบเข้าถึง วิธี "API" ของคลาสเพื่อนสามารถเป็นแบบส่วนตัวดังนั้นจึงไม่สามารถเข้าถึงได้นอกคลาสที่ต้องการการเข้าถึงแบบเพื่อน คำสั่งเดียวของมันคือการโทรไปยังสมาชิกที่ได้รับการป้องกันที่เป็นนามธรรมที่ชั้นส่งออกดำเนินการ

นี่คือรหัส:

ก่อนการทดสอบที่ตรวจสอบว่าใช้งานได้จริง:

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());
    }
}

จากนั้นบริการที่ต้องการให้เพื่อนเข้าถึงสมาชิกส่วนตัวของ Entity:

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();
    }
}

ในที่สุด: คลาส Entity ที่ให้การเข้าถึงที่เป็นมิตรกับสมาชิกส่วนตัวของแพ็กเกจเฉพาะกับคลาส 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();
            }
        };
    }
}

โอเคฉันต้องยอมรับมันนานกว่า "บริการเพื่อน :: บริการ;" แต่อาจเป็นไปได้ที่จะย่อให้สั้นลงในขณะที่ยังคงตรวจสอบเวลาคอมไพล์โดยใช้คำอธิบายประกอบ


วิธีนี้ใช้ไม่ได้กับคลาสปกติในแพ็คเกจเดียวกันที่เพิ่งจะได้รับ getFriend () จากนั้นเรียกใช้วิธีการที่ได้รับการป้องกันบายพาสไพรเวต
user2219808

1

ใน Java เป็นไปได้ที่จะมี "มิตรภาพที่เกี่ยวข้องกับแพ็คเกจ" สิ่งนี้สามารถเป็นประโยชน์สำหรับการทดสอบหน่วย หากคุณไม่ได้ระบุส่วนตัว / สาธารณะ / ป้องกันไว้ด้านหน้าของเมธอดมันจะเป็น "เพื่อนในแพ็คเกจ" คลาสในแพ็คเกจเดียวกันจะสามารถเข้าถึงได้ แต่จะเป็นส่วนตัวนอกชั้นเรียน

กฎนี้ไม่เป็นที่รู้จักเสมอและเป็นการประมาณที่ดีของคำสำคัญ "เพื่อน" C ++ ฉันคิดว่ามันเป็นการทดแทนที่ดี


1
นี่คือความจริง แต่ผมจริงๆขอเกี่ยวกับรหัสพำนักอยู่ในแพคเกจที่แตกต่างกัน ...
แมทธิวเมอร์ด็

1

ฉันคิดว่าคลาสเพื่อนใน C ++ เป็นเหมือนแนวคิดชั้นในใน Java การใช้คลาสภายในคุณสามารถกำหนดคลาสที่ล้อมรอบได้และคลาสที่ล้อมรอบ ชั้นที่ล้อมรอบมีการเข้าถึงแบบเต็มไปยังสมาชิกของรัฐและเอกชนของชั้นล้อมรอบ ดูลิงค์ต่อไปนี้: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html


เอ่อไม่พวกเขาไม่ได้ มันเหมือนมิตรภาพในชีวิตจริง: ทำได้ แต่ไม่จำเป็นต้องเป็นแบบร่วมกัน (การเป็นเพื่อนของ B ไม่ได้หมายความว่า B ถือเป็นเพื่อนของ A) และคุณและเพื่อนของคุณอาจแตกต่างกันโดยสิ้นเชิง ครอบครัวและมีกลุ่มเพื่อนของคุณเองซึ่งอาจเป็นไปได้ (แต่ไม่จำเป็น) (ไม่ใช่ว่าฉันต้องการดูชั้นเรียนที่มีเพื่อนมากมายมันเป็นคุณสมบัติที่มีประโยชน์ แต่ควรใช้ด้วยความระมัดระวัง)
Christopher Creutzig

1

ฉันคิดว่าวิธีการใช้รูปแบบการเข้าถึงของเพื่อนนั้นซับซ้อนเกินไป ฉันต้องเผชิญกับปัญหาเดียวกันและฉันแก้ไขโดยใช้ตัวสร้างสำเนาที่ดีเก่าที่รู้จักจาก C ++ ใน 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;
    }
}

ในใบสมัครของคุณคุณสามารถเขียนรหัสต่อไปนี้:

public class MyApp {

    private static class ProtectedAccessor extends ProtectedContainer {

        protected ProtectedAccessor() {
            super();
        }

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

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

ข้อดีของวิธีนี้คือมีเพียงแอปพลิเคชันของคุณเท่านั้นที่สามารถเข้าถึงข้อมูลที่ได้รับการป้องกัน มันไม่ใช่การแทนที่คำหลักเพื่อน แต่ฉันคิดว่ามันค่อนข้างเหมาะสมเมื่อคุณเขียนไลบรารี่ที่กำหนดเองและคุณต้องเข้าถึงข้อมูลที่ได้รับการป้องกัน

เมื่อใดก็ตามที่คุณต้องจัดการกับอินสแตนซ์ของ ProtectedContainer คุณสามารถห่อ ProtectedAccessor ที่อยู่รอบ ๆ และเข้าถึงได้

นอกจากนี้ยังทำงานร่วมกับวิธีการป้องกัน คุณกำหนดให้มันได้รับการป้องกันใน API ของคุณ ในภายหลังในใบสมัครของคุณคุณเขียนคลาส wrapper ส่วนตัวและเปิดเผยวิธีการป้องกันเป็นสาธารณะ แค่นั้นแหละ.


1
แต่ProtectedContainerสามารถ subclassed นอกแพ็คเกจ!
Raphael

0

หากคุณต้องการเข้าถึงวิธีที่ได้รับการป้องกันคุณสามารถสร้างคลาสย่อยของคลาสที่คุณต้องการใช้เพื่อแสดงวิธีการที่คุณต้องการใช้แบบสาธารณะ (หรือภายในเนมสเปซเพื่อให้ปลอดภัยยิ่งขึ้น) และมีอินสแตนซ์ของคลาสนั้นในชั้นเรียนของคุณ (ใช้เป็นพร็อกซี)

เท่าที่วิธีการส่วนตัวมีความกังวล (ฉันคิดว่า) คุณจะโชคไม่ดี


0

ฉันยอมรับว่าในกรณีส่วนใหญ่คำหลักของเพื่อนนั้นไม่จำเป็น

  • Package-private (aka. default) นั้นเพียงพอในกรณีส่วนใหญ่ที่คุณมีกลุ่มคลาสที่มีการเชื่อมโยงกันอย่างมาก
  • สำหรับคลาสดีบักที่ต้องการเข้าถึง internals ฉันมักจะทำให้วิธีการส่วนตัวและเข้าถึงได้ผ่านการสะท้อนกลับ ความเร็วมักจะไม่สำคัญที่นี่
  • บางครั้งคุณใช้วิธีการที่เป็น "แฮ็ค" หรืออื่น ๆ ที่อาจมีการเปลี่ยนแปลง ฉันกำหนดให้เป็นแบบสาธารณะ แต่ใช้ @Deprecated เพื่อระบุว่าคุณไม่ควรเชื่อถือวิธีนี้ที่มีอยู่

และในที่สุดถ้าจำเป็นจริงๆมีรูปแบบการเข้าถึงของเพื่อนที่กล่าวถึงในคำตอบอื่น ๆ


0

ไม่ได้ใช้คำหลักหรือมากกว่านั้น

คุณสามารถ "โกง" โดยใช้การสะท้อนภาพ ฯลฯ แต่ฉันไม่แนะนำ "การโกง"


3
ฉันจะพิจารณาเรื่องนี้เป็นความคิดที่ไม่ดีที่แม้จะบอกว่ามันน่ารังเกียจสำหรับฉัน เห็นได้ชัดว่านี่เป็นความคิดที่ดีที่สุดและไม่ควรเป็นส่วนหนึ่งของการออกแบบใด ๆ
shsteimer

0

วิธีการที่ฉันพบในการแก้ปัญหานี้คือการสร้างวัตถุ accessor เช่น:

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();
    }
}

รหัสแรกที่เรียกว่าgetAccessor()"อ้างสิทธิ์ความเป็นเจ้าของ" ของ accessor โดยปกติแล้วนี่คือรหัสที่สร้างวัตถุ

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

นอกจากนี้ยังมีความได้เปรียบกว่าเพื่อนกลไก c ++ 's เพราะมันช่วยให้คุณสามารถ จำกัด การเข้าถึงในต่ออินสแตนซ์ระดับเมื่อเทียบกับต่อระดับระดับ โดยการควบคุมการอ้างอิง accessor คุณสามารถควบคุมการเข้าถึงวัตถุ นอกจากนี้คุณยังสามารถสร้าง accessor หลายตัวและให้การเข้าถึงที่แตกต่างกันไปซึ่งอนุญาตให้ควบคุมอย่างละเอียดว่าโค้ดใดที่สามารถเข้าถึงอะไร:

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();
    }
}

สุดท้ายหากคุณต้องการจัดระเบียบให้มากกว่านี้คุณสามารถสร้างออบเจ็กต์อ้างอิงซึ่งรวมทุกอย่างเข้าด้วยกัน สิ่งนี้ช่วยให้คุณสามารถอ้างสิทธิ์ accessors ทั้งหมดด้วยการเรียกใช้เมธอดเดียวรวมถึงเก็บอินสแตนซ์ที่เชื่อมโยงไว้ด้วย เมื่อคุณมีการอ้างอิงคุณสามารถส่ง accessors ออกไปยังรหัสที่ต้องการได้:

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();
    }
}

หลังจากการต่อสู้ที่หัวมาก (ไม่ใช่คนดี) นี่เป็นคำตอบสุดท้ายของฉันและฉันชอบมันมาก มันมีความยืดหยุ่นใช้งานง่ายและช่วยให้สามารถควบคุมการเข้าถึงชั้นเรียนได้เป็นอย่างดี (ในที่มีการอ้างอิงเท่านั้นเข้าถึงเป็นประโยชน์อย่างมาก.) หากคุณใช้การป้องกันแทนส่วนตัวสำหรับ accessors / อ้างอิงย่อยชั้นเรียนของ Foo getReferenceยังสามารถกลับมาขยายการอ้างอิงจาก มันไม่ต้องการการสะท้อนใด ๆ ดังนั้นจึงสามารถใช้ในสภาพแวดล้อมใด ๆ



0

ฉันชอบการมอบหมายหรือองค์ประกอบหรือคลาสโรงงาน (ขึ้นอยู่กับปัญหาที่ทำให้เกิดปัญหานี้) เพื่อหลีกเลี่ยงการทำให้เป็นคลาสสาธารณะ

หากเป็นปัญหา "อินเทอร์เฟซ / คลาสการใช้งานในแพ็คเกจที่แตกต่างกัน" ฉันจะใช้คลาสโรงงานสาธารณะที่จะอยู่ในแพคเกจเดียวกันกับแพ็คเกจ impl และป้องกันการเปิดเผยคลาส impl

ถ้าเป็น "ฉันเกลียดที่จะทำให้คลาส / วิธีนี้เป็นแบบสาธารณะเพียงเพื่อให้ฟังก์ชันนี้สำหรับคลาสอื่นในแพ็คเกจอื่น" ปัญหาจากนั้นฉันจะใช้คลาสตัวแทนสาธารณะในแพ็คเกจเดียวกันและแสดงเฉพาะส่วนนั้นของฟังก์ชัน ต้องการโดยคลาส "คนนอก"

การตัดสินใจเหล่านี้บางส่วนได้รับแรงผลักดันจากสถาปัตยกรรมการโหลดคลาสเซิร์ฟเวอร์เป้าหมาย (บันเดิล OSGi, WAR / EAR เป็นต้น) การปรับใช้และแบบแผนการตั้งชื่อแพ็คเกจ ตัวอย่างเช่นโซลูชันที่เสนอด้านบนรูปแบบ 'Friend Accessor' นั้นฉลาดสำหรับแอปพลิเคชัน Java ปกติ ฉันสงสัยว่ามันยากที่จะใช้ใน OSGi หรือไม่เนื่องจากความแตกต่างในสไตล์การโหลดคลาส


0

ฉันไม่รู้ว่ามันเป็นของใช้ที่ใครก็ได้ แต่ฉันจัดการมันด้วยวิธีต่อไปนี้:

ฉันสร้างส่วนต่อประสาน (AdminRights)

ทุกคลาสที่ควรจะเรียกใช้ฟังก์ชั่นที่กล่าวมานั้นควรใช้งาน AdminRights

จากนั้นฉันก็สร้างฟังก์ชั่น HasAdminRights ดังนี้

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

ฉันเคยเห็นวิธีแก้ปัญหาแบบสะท้อนกลับซึ่งทำ "การตรวจสอบเพื่อน" ที่รันไทม์โดยใช้การสะท้อนและตรวจสอบการเรียกสแต็กเพื่อดูว่าการเรียกคลาสนั้นได้รับอนุญาตให้ทำเช่นนั้นหรือไม่ เป็นการตรวจสอบรันไทม์ แต่ก็มีข้อเสียเปรียบที่ชัดเจน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.