2016-02-20 11 views
5

Ich führe Schablonenmuster in Java ein. Lassen Sie uns das folgende Stück Code annehmen:Schützende Haken im Schablonenmuster

public abstract class A { 

    public final void work() { 
    doPrepare(); 
    doWork(); 
    } 

    protected abstract void doPrepare(); 

    protected abstract void doWork(); 
} 

public final class B extends A { 

    @Override 
    protected abstract void doPrepare() { /* do stuff here */ } 

    @Override 
    protected abstract void doWork() { /* do stuff here */ } 
} 

public final class Main { 

    public static void main(String[] args) { 
    B b = new B(); 
    b.work(); 
    } 
} 

halte ich es für ein Problem, dass man versehentlich b.doWork() leicht in der Lage ist b.work() stattdessen zu nennen, immer anrufen. Was ist die eleganteste Lösung, wenn möglich, die Haken zu "verstecken"?

+0

"ein leicht in der Lage ist, aus Versehen b.doWork nennen()". "Ja wirklich?" Es ist geschützt, also können nur A- oder B-Instanzen es anrufen. –

+0

Leider, da es geschützt ist, können alle Klassen aus dem Paket es nennen. Pakete zu splitten ist ein Weg zu gehen, obwohl ich eher vermeiden möchte, 1- oder 2-Klasse-Pakete zu haben. –

+0

Nein! Sie beschreiben den Standardzugriffsmodifikator (d. H. Keiner). Protected bedeutet "nur ich oder Klassen, die mich erweitern können diese verwenden" –

Antwort

2

Die einfachste und mehr offensichtlich, was Sie sein tun würde, könnten Ihre A -> B Hierarchie von einem anderen Paket zu verwenden.

Wenn Sie aus irgendeinem Grund nicht können, können Sie eine Schnittstelle verwenden und Ihre Klasse B die Klasse wickeln, die tatsächlich von A stammt. Etwas wie folgt aus:

public interface Worker { 

    void work(); 
} 

public abstract class A implements Worker { 

    @Override 
    public final void work() { 
     doPrepare(); 
     doWork(); 
    } 

    protected abstract void doPrepare(); 

    protected abstract void doWork(); 
} 

public static class B implements Worker { // does not extend A, but implements Worker 

    private final A wrapped = new A() { // anonymous inner class, so it extends A 

     @Override 
     protected void doPrepare() { 
      System.out.println("doPrepare"); 
     } 

     @Override 
     protected void doWork() { 
      System.out.println("doWork"); 
     } 
    }; 

    @Override 
    public void work() { // delegate implementation to descendant from A 
     this.wrapped.work(); 
    } 
} 

Auf diese Weise geschützten Methoden bleiben in einer anonymen inneren Klasse verborgen, die von A erstreckt, die ein privater Endglied B ist. Der Schlüssel ist, dass B nicht A erweitert, aber implementiert die Worker Schnittstelle work() Methode durch Delegieren an die anonyme innere Klasse.Selbst wenn die Methode Bwork() von einer Klasse aufgerufen wird, die zum selben Paket gehört, sind geschützte Methoden nicht sichtbar.

B b = new B(); 
b.work(); // this method is accessible via the Work interface 

Ausgang:

doPrepare 
doWork 
+0

Ich habe versucht und meinen Code angepasst, um diesem Ansatz zu folgen, und es funktioniert wie ein Charme, danke. Es funktioniert auch mit Generika. Der einzige Nachteil, den ich gefunden habe, ist, dass Sie für jede Methode in der abstrakten Klasse A einen Wrapper bereitstellen müssen; etwas, das Sie vermeiden können, wenn Sie Vererbung verwenden. Aber das ist definitiv ein angenehmer Kompromiss. –

3

Sie verwenden tatsächlich das Vorlagenmuster. Es gibt zwei Haupt Dinge, die Sie von externen Benutzern zu „verstecken“ Details machen könnte:

1) Verstehen Sie Ihre access modifiers:

    Access Levels 
Modifier | Class | Package | Subclass | World | 
-------------------------------------------------- 
public  | Y  | Y  | Y  | Y  | 
protected | Y  | Y  | Y  | N  | 
no modifier | Y  | Y  | N  | N  | 
private  | Y  | N  | N  | N  | 

Kurz entschlossener Hacker Mobbing ihren Weg in Vergangenheit diesen Schutz mit Reflexion, können diese Achten Sie darauf, dass zufällige Programmierer versehentlich Methoden aufrufen, die ihnen am besten verborgen bleiben. Die Programmierer werden dies zu schätzen wissen. Niemand möchte eine überladene API verwenden.

Derzeit b.doWork() ist protected. So wäre es nur in Ihrem main() sichtbar, wenn Ihr main() in Klasse A (ist es nicht), Unterklasse B (ist es nicht), oder in dem gleichen Paket (nicht klar, Sie haben keine Hinweise über das Paket (s) posten Ihr Code befindet sich in).

Dies wirft die Frage auf, wen Sie vor dem Sehen schützen möchten? Wenn das von Ihnen erstellte Paket nur von Ihnen entwickelt wird, ist es möglicherweise nicht so schlimm. Wenn viele Leute in diesem Paket mit vielen anderen Klassen herumstampfen, ist es unwahrscheinlich, dass sie mit den Klassen A und B vertraut sind. Dies ist der Fall, wenn Sie nicht wollen, dass alles rumhängt, wo es jeder sehen kann. Hier wäre es schön, nur Klassen- und Unterklassenzugriff zu erlauben. Aber es gibt keinen Modifikator, der den Zugriff auf Unterklassen ermöglicht, ohne auch den Paketzugang zuzulassen. Solange Sie Unterklasse protected sind, ist das Beste, was Sie tun können. In diesem Sinne sollten Sie keine Pakete mit einer großen Anzahl von Klassen erstellen. Halten Sie Pakete fest und fokussiert. Auf diese Weise werden die meisten Leute außerhalb des Pakets arbeiten, wo die Dinge schön und einfach aussehen.

Kurz gesagt, wenn Sie sehen möchten, main() verhindert, rufen Sie main() in einem anderen Paket.

package some.other.package 

public final class Main { 
... 
} 

2) Der Wert dieses nächsten Ratschlag wird schwer sein, mit Ihrem aktuellen Code zu verstehen, denn es geht um die Lösung von Problemen ist, die nur kommen wird, wenn Ihr Code auf erweitert. Aber es ist wirklich eng mit der Idee von Menschen aus den Dingen zu sehen, wie b.doWork()

What does "program to interfaces, not implementations" mean?

in Ihrem Code zu schützen, was das bedeutet, ist es besser wäre, wenn Haupt sieht wie folgt aus:

public static void main(String[] args) { 
    A someALikeThingy = new B(); // <-- note use of type A 
    someALikeThingy.work();  //The name is silly but shows we've forgotten about B 
           //and know it isn't really just an A :) 
} 

Warum? Was macht die Verwendung des Typs A im Vergleich zum Typ B von Bedeutung? Nun A ist näher an eine Schnittstelle. Beachten Sie, hier meine ich nicht nur, was Sie bekommen, wenn Sie das Schlüsselwort interface verwenden.Ich meine Typ B ist eine konkrete Implementierung des Typs A und wenn ich nicht absolut Code gegen B schreiben muss, würde ich wirklich lieber nicht, weil wer weiß, was komisch nicht A Dinge B hat. Dies ist hier schwer zu verstehen, da der Code in main kurz ist & süß. Auch die öffentlichen SchnittstellenA und B sind nicht so unterschiedlich. Sie haben beide nur eine öffentliche Methode work(). Stellen Sie sich vor, dass main() lang und gewunden war, Imagine B hatte alle Arten von nicht A Methoden hängen davon ab. Stellen Sie sich nun vor, Sie müssen eine Klasse C erstellen, mit der Sie hauptsächlich arbeiten möchten. Wäre es nicht beruhigend zu sehen, dass während der Hauptleitung eine B, dass so weit wie jede Zeile in der Hauptsache, dass B ist nur einige einfache A wie etwas gefüttert wurde. Außer für den Teil nach der new könnte dies wahr sein und Sie davor bewahren, irgendetwas zu tun main(), aber das new zu aktualisieren. Ist das nicht der Grund, warum eine stark typisierte Sprache manchmal schön sein kann?

<rant>
Wenn Sie denken auch, dass ein Teil nach new mit dem B() ist zu viel Arbeit zu aktualisieren Sie nicht allein sind. Diese besondere kleine Obsession hat uns dazu gebracht, die dependency injection movement, die eine Reihe von Frameworks, die wirklich mehr über die Automatisierung der Objektkonstruktion als eine einfache Injektion, dass jeder Konstruktor Sie tun können, war hervorgebracht.
</rant >

Update:

ich Ihre Kommentare sehen, die Sie zögern, kleinen engen Pakete zu erstellen. Ziehen Sie in diesem Fall in Erwägung, ein vererbungsbasiertes Vorlagenmuster zugunsten eines zusammensetzungsbasierten Strategie-Patterns aufzugeben. Sie bekommen immer noch Polymorphie. Es passiert einfach nicht umsonst. Etwas mehr Code schreiben und Indirection. Aber Sie können den Status sogar zur Laufzeit ändern.

Dies scheint kontraintuitiv zu sein, denn jetzt muss doWork() öffentlich sein. Was für ein Schutz ist das? Nun können Sie B ganz hinter oder sogar innerhalb AHandler verstecken. Dies ist eine menschenfreundliche Fassadenklasse, die den ursprünglichen Vorlagencode enthält, aber nur work() darstellt. A kann nur ein interface sein, so dass die AHandler immer noch nicht weiß, dass es eine B hat. Dieser Ansatz ist bei der Abhängigkeitsinjektionsmenge beliebt, hat jedoch sicherlich keine universelle Anziehungskraft. Manche tun es jedes Mal blind, was viele ärgert. Hier können Sie mehr darüber lesen: Prefer composition over inheritance?

+0

Vielen Dank für eine erschöpfende Antwort. Ich werde versuchen, alle Ideen zu adressieren: 1) Access-Modifikatoren und Paketstruktur war das erste, was ich lange vor dem Posten der Frage berücksichtigt habe. Mein Paket ist bereits klein, und eine weitere Aufteilung würde mir 1- oder 2-Klassen-Pakete geben, wie ich in einem meiner vorherigen Kommentare erwähnt habe. Das ist also keine Lösung in meinem aktuellen Fall, sondern könnte für jemand anderen in der Zukunft sein. –

+0

2) Was den Schnittstellenansatz betrifft, hat es zwei Nachteile. Erstens, es funktioniert nicht gut mit Generika (ich könnte ein Beispiel geben). Zweitens hindert es Sie daran zu schreiben: 'Ergebnis r = neu B (args) .work();', was ich für schöner halte als etwas forciert 'A a = neu B (args); Ergebnis r = a.work(); '. Dies ist wieder etwas, was ich versucht habe und immer noch nach etwas Besserem suchte, obwohl es für einige akzeptabel sein könnte. –

+0

3) Komposition ist wirklich die Antwort. Ich bin aufgrund der Semantik meiner Klassen direkt in die Erbschaft gesprungen und habe vergessen, diese Option in Betracht zu ziehen. Ich nenne die Antwort von Herrn Schaffner als richtig, da sie ein Beispiel gibt, und behält begrenzten (geschützten) Zugang zu den Haken bei; aber ich empfehle anderen, sich die von Ihnen geposteten Links anzusehen. –