2016-02-20 9 views
5

Я реализую шаблон шаблона в Java. Допустим, следующий фрагмент кода:Защитные крючки в шаблоне шаблона

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

Я считаю, что проблема, что человек легко может случайно позвонить b.doWork() вместо того, чтобы всегда называя b.work(). Какое самое элегантное решение, если возможно, «спрятать» крючки?

+0

"один легко сможет случайно называют b.doWork()". В самом деле? Он защищен, поэтому его могут вызвать только экземпляры A или B. –

+0

К сожалению, поскольку он защищен, все классы из пакета могут его вызвать. Разделение пакетов - это путь, хотя я бы предпочел избегать использования пакетов с 1 или 2 классами. –

+0

Нет !! Вы описываете модификатор доступа по умолчанию (т. Е. Нет). Защищенные средства «только меня или классы, которые расширяют меня, могут использовать это» –

ответ

2

Простейшей и очевидной вещью, которую вы могли бы сделать, было бы использование вашей иерархии A -> B из другого пакета.

Если по какой-либо причине вы не можете этого сделать, вы можете использовать интерфейс и сделать класс B классом, который фактически спускается с A. Что-то вроде этого:

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

Таким образом, защищенные методы остаются скрытыми в анонимном внутреннем классе, который простирается от A, который является частным окончательным членом B. Ключ состоит в том, что B не расширяет A, но реализует метод Worker интерфейса work() путем делегирования анонимному внутреннему классу.Таким образом, даже в случае B метод work() вызывается из класса, принадлежащего к одному и тому же пакету, защищенные методы не будут видны.

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

Выход:

doPrepare 
doWork 
+0

Я попытался и скорректировал свой код, чтобы следовать этому подходу, и он работает как шарм, спасибо. Он также работает с дженериками. Единственный недостаток, который я нашел, заключается в том, что для каждого метода в абстрактном классе A вам необходимо предоставить оболочку; что вы можете избежать, когда используете наследование. Но это, безусловно, приятный компромисс. –

3

Вы действительно используете шаблон шаблона. Есть две основных вещей, которые вы могли бы сделать, чтобы «скрыть» деталь от внешних пользователей:

1) Понимание ваших 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  | 

Замыкания детерминированных хакеров издевательств своего пути в прошлом этих защит с отражением, это может держите случайных кодеров от случайно вызывающих методов, которые лучше всего держать вне их пути. Кодеры оценят это. Никто не хочет использовать загроможденный API. В настоящее время b.doWork() является protected. Так что это будет видно только в вашем main(), если ваш main() находится в классе A (это не так), подкласс B (это не так) или в том же пакете (непонятно, что вы не опубликовали никаких сведений о пакете (пакетах) ваш код находится в).

Это вопрос, который вы пытаетесь защитить от наблюдения b.doWork()? Если создаваемый вами пакет - это то, что вы разрабатываете, это может быть не так уж плохо. Если многие люди топают в этом пакете, возившись со многими другими классами, вряд ли они будут близко знакомы с классами A и B. Это тот случай, когда вы не хотите, чтобы все висели там, где каждый может это увидеть. Здесь было бы неплохо разрешить доступ только класса и подкласса. Но не существует модификатора, который позволяет доступ к подклассам без разрешения доступа к пакету. Пока вы подклассифицируете protected - это лучшее, что вы можете сделать. Имея это в виду, не создавайте пакеты с огромным количеством классов. Держите упаковки плотными и целенаправленными. Таким образом, большинство людей будет работать вне пакета, где все выглядит красиво и просто.

В двух словах, если вы хотите, чтобы main() не позвонил b.doWork() положил main() в другой упаковке.

package some.other.package 

public final class Main { 
... 
} 

2) Значение этого следующего куска совета будет трудно понять, с текущим кодом, потому что речь идет о решении вопросов, которые будут только придумывают, если ваш код расширяются дальше. Но это на самом деле тесно связана с идеей защиты людей от видящих вещей, как b.doWork()

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

В коде это означает, что было бы лучше, если бы главный выглядел так:

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

Зачем? Что использует тип A против типа B вопрос? Ну A ближе к интерфейс. Заметьте, здесь я не просто имею в виду то, что вы получаете, когда используете ключевое слово interface.Я имею в виду, что тип B представляет собой конкретную реализацию типа A, и если мне не обязательно писать код против B, то я бы предпочел не потому, что кто знает, что странно, но не A вещей B имеет. Это трудно понять здесь, потому что код в основном короткий & сладкий. Кроме того, общественность интерфейсов от A и B не так уж и отличается. Они оба имеют только один общедоступный метод work(). Представьте себе, что main() был длинным и запутанным. Представьте себе, что B имел всевозможные методы, не зависящие от него. Теперь представьте, что вам нужно создать класс C, с которым вы хотите справиться. Разве не было бы приятно видеть, что в то время как главная была подана B, что, насколько каждая строка в главном знает, что B - это просто простая вещь A. За исключением части после new, это может быть правдой и избавит вас от необходимости делать что-либо до main(), но обновление этого new. Действительно, разве это не значит, что использование строго типизированного языка иногда может быть приятным?

< декламация >
Если вы думаете, даже обновить эту часть после new с B() слишком много работы, вы не одиноки. Эта особая навязчивая идея - вот что дало нам dependency injection movement, который породил кучу фреймворков, которые были в большей степени связаны с автоматизацией построения объектов, чем простая инъекция, которую может позволить любой конструктор.
</декламация >

Update:

Я вижу из ваших комментариев, что вы неохотно создавать небольшие плотные пакеты. В этом случае рассмотрите отказ от шаблона шаблона, основанного на наследовании, в пользу шаблона стратегии на основе композиции. Вы все еще получаете полиморфизм. Это просто не происходит бесплатно. Еще немного написания кода и косвенности. Но вы можете изменить состояние даже во время работы.

Это будет казаться противостоящим интуитивно понятным, потому что теперь doWork() должно быть общедоступным. Что это за защита? Ну теперь вы можете скрыть B полностью за или даже внутри AHandler. Это какой-то дружелюбный к человеку класс фасадов, который содержит то, что раньше было вашим шаблоном, но выставляет только work(). A может быть просто interface, так что AHandler до сих пор не знает, что он имеет B. Этот подход популярен среди толпы инъекций зависимостей, но, конечно же, не имеет универсальной привлекательности. Некоторые делают это слепо каждый раз, что раздражает многих. Вы можете узнать больше об этом здесь: Prefer composition over inheritance?

+0

Спасибо за исчерпывающий ответ. Я попытаюсь рассмотреть все идеи: 1) Модификаторы доступа и структура пакета были первым, что я рассмотрел задолго до публикации вопроса. Мой пакет уже невелик, и его дальнейшее разбиение даст мне 1- или 2-классные пакеты, как я упомянул в одном из моих предыдущих комментариев. Так что это не решение в моем текущем случае, но может быть для кого-то еще в будущем. –

+0

2) Что касается интерфейса, он имеет два недостатка. Во-первых, это не очень хорошо работает с дженериками (я мог бы привести пример). Во-вторых, это мешает вам писать: «Результат r = новый B (args) .work();', который я считаю более приятным, чем несколько вынужденным A A = new B (args); Результат r = a.work(); '. Это снова то, что я пробовал и все еще искал что-то лучшее, хотя это может быть приемлемым для некоторых. –

+0

3) Композиция - это действительно ответ. Я перешел прямо к наследованию из-за семантики моих классов и забыл рассмотреть этот вариант. Я отмечаю ответ г-на Шаффнера как правильный, поскольку он дает пример, и сохраняет ограниченный (защищенный) доступ к крючкам; но я рекомендую другим взглянуть на ссылки, которые вы опубликовали. –

Смежные вопросы