이 글에서는 주로 IOC 제어 반전을 활용한 Java의 세 가지 디자인 패턴을 자세히 소개합니다. 참고할만한 가치가 있으니 관심 있는 친구들이 참고해 보세요.
제어 반전(IoC)은 많은 개발자에게 있어 개념이 거의 없거나 모호한 개념입니다. 실제 적용이 없습니다. 최선의 경우 IoC(제어 반전)는 DI(종속성 주입)와 동일하다고 생각할 수 있습니다. 실제로 반전 제어와 종속성 주입은 양쪽에 반전된 종속성 관리 제어만 반영하는 경우에만 동일한 것으로 간주됩니다. 그러나 종속성 주입은 실제로 잘 알려진 IoC 형태입니다. 그러나 실제로 IoC는 다양한 패턴을 통해 구현할 수 있는 비교적 광범위한 소프트웨어 설계 패러다임입니다. 이번 글에서는 종속성 주입, 관찰자 패턴, 템플릿 메서드 패턴이 제어 역전을 구현하는 방법을 소개합니다.
다양한 사용 시나리오에서 요약된 다른 많은 디자인 패턴과 마찬가지로 IoC 구현도 개발자에게 적합한 유사한 절충안입니다.
한편으로는 고도로 분리된 구성 요소 디자인 및 애플리케이션 로직을 단일로 캡슐화합니다. place는 IoC를 구현하는 직접적이고 자연스러운 방법입니다.
반면 위의 구현에는 최소한 하나의 간접 계층을 구축해야 하는데, 이는 일부 사용 사례에서 과도한 설계가 될 수 있습니다.
다음으로 이러한 속성 간의 균형을 맞추는 방법을 이해하는 데 도움이 되는 몇 가지 구체적인 구현을 살펴보겠습니다.
IOC 패러다임 공개
제어 반전은 특정 특성을 지닌 패턴입니다. 아래에는 콘솔에서 사용자 데이터를 수집하는 기능을 구현하는 Martin Fowler가 제공한 고전적인 IOC 예제가 나와 있습니다.
public static void main(String[] args) { while (true) { BufferedReader userInputReader = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Please enter some text: "); try { System.out.println(userInputReader.readLine()); } catch (IOException e) { e.printStackTrace(); } } }
이 사용 사례에서는 흐름 제어가 기본 메서드에서 수행됩니다. 무한 루프 호출에서는 사용자 입력을 읽고 읽은 내용을 콘솔에 출력합니다. 기본 메서드는 사용자 입력을 읽을 시기와 출력 시기를 완전히 제어합니다.
위 프로그램의 새 버전을 고려해 보세요. 이 버전에서는 그래픽 인터페이스의 텍스트 상자를 통해 사용자 입력을 받아야 합니다. 또한 액션 리스너가 바인딩된 버튼도 있습니다. 이 경우 사용자가 버튼을 클릭할 때마다 입력된 텍스트가 리스너에 의해 수집되어 패널에 인쇄됩니다.
이 버전의 프로그램에서는 실제로 이벤트 리스너 모델(이 경우 프레임워크)의 제어를 받아 개발자가 작성한 코드를 호출하여 사용자 입력을 읽고 인쇄합니다. 간단히 말해서, 프레임워크는 개발자의 코드를 호출하지만 그 반대는 아닙니다. 프레임워크는 실제로 개발자에게 사용자 정의 코드 조각을 삽입하기 위한 일련의 진입점을 제공하는 확장 가능한 구조입니다.
이 경우 통제가 효과적으로 반전되었습니다.
보다 일반적인 관점에서 프레임워크(인터페이스 구현, 구현 상속(하위 클래스라고도 함)의 형태로)에 의해 정의된 각 호출 가능한 확장 지점은 잘 정의된 IoC 형태입니다.
다음의 간단한 서블릿 예제를 보세요:
public class MyServlet extends HttpServlet { protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // developer implementation here } protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // developer implementation here } }
여기서 HttpServlet 클래스(프레임워크에 속함)는 프로그램을 완전히 제어하는 요소이지 MyServlet 하위 클래스가 아닙니다. 서블릿 컨테이너에 의해 생성된 후 서블릿의 GET 및 POST HTTP 요청이 수신되면 doGet() 및 doPost() 메서드의 코드가 각각 자동으로 호출됩니다.
하위 클래스가 기본 클래스가 아닌 컨트롤의 요소인 일반적인 상속 접근 방식과 비교하여 이 예에서는 컨트롤이 반전되었습니다.
사실 서블릿 메소드는 템플릿 메소드 패턴을 구현한 것인데 이에 대해서는 나중에 자세히 다루겠습니다.
확장 가능한 API를 제공하여 개방형-폐쇄형 원칙을 준수하는 프레임워크를 사용할 때 프레임워크를 사용하는 개발자의 역할은 궁극적으로 자체 사용자 정의 클래스 세트를 정의하는 것으로 귀결됩니다. 즉, 개발자는 프레임워크에서 제공하는 클래스를 구현하거나 다중 클래스를 구현합니다. 인터페이스를 사용하거나 기존 기본 클래스에서 상속합니다. 그러면 클래스 인스턴스는 프레임워크에 의해 직접 인스턴스화되고 이러한 인스턴스는 프레임워크에 의해 호출됩니다.
Fowler의 말을 인용하자면: 개발자가 프레임워크를 호출하는 것이 아니라 프레임워크가 개발자를 호출합니다. 따라서 IoC는 할리우드 원칙이라고도 불립니다. 전화하지 마세요. 전화하겠습니다.
IOC 구현 방법
이 문제와 관련하여 제어 역전을 구현하는 방법에는 여러 가지가 있음이 분명합니다. 일반적인 구현 방법을 요약할 수도 있습니다.
IOC 구현을 위한 종속성 주입
앞서 언급했듯이 종속성 주입은 IOC를 구현하는 방법이자 가장 일반적인 객체지향 설계 방법입니다. 하지만 생각해 보세요. 종속성을 주입하면 제어 효과 반전이 어떻게 달성되나요?
이 질문에 답하기 위해 다음과 같은 원래 예를 제공합니다.
public interface UserQueue { void add(User user); void remove(User user); User get(); } public abstract class AbstractUserQueue implements UserQueue { protected LinkedList<User> queue = new LinkedList<>(); @Override public void add(User user) { queue.addFirst(user); } @Override public void remove(User user) { queue.remove(user); } @Override public abstract User get(); } public class UserFifoQueue extends AbstractUserQueue { public User get() { return queue.getLast(); } } public class UserLifoQueue extends AbstractUserQueue { public User get() { return queue.getFirst(); } }
UserQueue 인터페이스는 대기열에 사용자 개체를 저장하기 위한 공개 API를 정의합니다(단순성과 명확성을 위해 여기에서는 특정 구현에서 사용자를 무시합니다). AbstractUserQueue는 후속 상속 클래스에 대한 일부 공용 메소드 구현을 제공합니다. 마지막 UserFifoQueue 및 UserLifoQueue는 각각 FIFO 및 LIFO 대기열을 구현합니다.
这是,实现子类多态性的一种有效方式。但是这具体用什么来买我们好处呢?实际上,好处还是蛮多的。
通过创建一个依赖于UserQueue抽象类型(也称为DI术语中的服务)的客户端类,可以在运行时注入不同的实现,无需会重构使用客户端类的代码:
public class UserProcessor { private UserQueue userQueue; public UserProcessor(UserQueue userQueue) { this.userQueue = userQueue; } public void process() { // process queued users here } }
UserProcessor展示了,注入依赖确实是IOC的一种方式。
我们可以通过一些硬编码方式 如 new 操作,直接在构造函数中实例化在UserProcessor中获取对队列的依赖关系。但是,这是典型的代码硬编程,它引入了客户端类与其依赖关系之间的强耦合,并大大降低了可测性。耳边警钟声声想起啦!不是吗?是的,这样设计真的很挫。
该类在构造函数中声明对抽象类 UserQueue 的依赖。也就是说,依赖关系不再通过 在构造函数中使用 new 操作, 相反,通过外部注入的方式,要么使用依赖注入框架(如CDI和谷歌的Guice),要么使用factory或builders模式。
简而言之,使用DI,客户端类的依赖关系的控制,不再位于这些类中;而是在注入器中进行:
public static void main(String[] args) { UserFifoQueue fifoQueue = new UserFifoQueue(); fifoQueue.add(new User("user1")); fifoQueue.add(new User("user2")); fifoQueue.add(new User("user3")); UserProcessor userProcessor = new UserProcessor(fifoQueue); userProcessor.process(); }
上述方式达到了预期效果,而且对UserLifoQueue的注入也简单明了。显而易见,DI确实是实现IOC的一种方式(该例中,DI是实现IOC的一个中间层)。
观察者模式实现IOC
直接通过观察者模式实现IOC,也是一种常见的直观方式。广义上讲,通过观察者实现IOC,与前文提到的通过GUI界面中的action监听器方式类似。但是在使用action监听器情况下,只有在特定的用户事件发生时(点击鼠标,键盘或窗口事件等),才会发生调用。观察者模式通常用于在模型视图的上下文中,跟踪模型对象的状态的变迁。
在一个典型的实现中,一到多个观察者绑定到可观察对象(也称为模式术语中的主题),例如通过调用addObserver方法进行绑定。一旦定义了被观察者和观察者之间的绑定,则被观察者状态的变迁都会触发调用观察者的操作。
为了深入了解这个概念,给出如下例子:
@FunctionalInterface public interface SubjectObserver { void update(); }
值发生改变时,会触发调用上述这个很简单的观察者。真实情况下,通常会提供功能更丰富的API,如需要保存变化的实例,或者新旧值,但是这些都不需要观察action(行为)模式,所以这里举例尽量简单。
下面,给出一个被观察者类:
public class User { private String name; private List<SubjectObserver> observers = new ArrayList<>(); public User(String name) { this.name = name; } public void setName(String name) { this.name = name; notifyObservers(); } public String getName() { return name; } public void addObserver(SubjectObserver observer) { observers.add(observer); } public void deleteObserver(SubjectObserver observer) { observers.remove(observer); } private void notifyObservers(){ observers.stream().forEach(observer -> observer.update()); } }
User类中,当通过setter方法变更其状态事,都会触发调用绑定到它的观察者。
使用主题观察者和主题,以下是实例给出了观察方式:
public static void main(String[] args) { User user = new User("John"); user.addObserver(() -> System.out.println( "Observable subject " + user + " has changed its state.")); user.setName("Jack"); }
每当User对象的状态通过setter方法进行修改时,观察者将被通知并向控制台打印出一条消息。到目前为止,给出了观察者模式的一个简单用例。不过,通过这个看似简单的用例,我们了解到在这种情况下控制是如何实现反转的。
观察者模式下,主题就是起到”框架层“的作用,它完全主导何时何地去触发谁的调用。观察者的主动权被外放,因为观察者无法主导自己何时被调用(只要它们已经被注册到某个主题中的话)。这意味着,实际上我们可以发现控制被反转的”事发地“ – - – 当观察者绑定到主题时:
user.addObserver(() -> System.out.println( "Observable subject " + user + " has changed its state."));
上述用例,简要说明了为什么,观察者模式(或GUI驱动环境中的action监听器)是实现IoC的一种非常简单的方式。正是以这种分散式设计软件组件的形式,使得控制得以发生反转。
通过模板方法模式实现IoC
模板方法模式实现的思想是在一个基类中通过几个抽象方法(也称算法步骤)来定义一个通用的算法,然后让子类提供具体的实现,这样保证算法结构不变。
我们可以应用这个思想,定义一个通用的算法来处理领域实体:
public abstract class EntityProcessor { public final void processEntity() { getEntityData(); createEntity(); validateEntity(); persistEntity(); } protected abstract void getEntityData(); protected abstract void createEntity(); protected abstract void validateEntity(); protected abstract void persistEntity(); }
processEntity() 方法是个模板方法,它定义了处理实体的算法,而抽象方法代表了算法的步骤,它们必须在子类中实现。通过多次继承 EntityProcessor 并实现不同的抽象方法,可以实现若干算法版本。
虽然这说清楚了模板方法模式背后的动机,但人们可能想知道为什么这是 IoC 的模式。
일반적인 상속에서 하위 클래스는 기본 클래스에 정의된 메서드를 호출합니다. 이 모드에서 상대적으로 실제 상황은 하위 클래스에 의해 구현된 메서드(알고리즘 단계)가 기본 클래스의 템플릿 메서드에 의해 호출되는 것입니다. 따라서 컨트롤은 실제로 하위 클래스가 아닌 기본 클래스에 있습니다.
이것 역시 계층 구조로 구현된 IoC의 전형적인 예입니다. 이 경우 템플릿 메서드는 개발자가 자신의 구현 세트를 관리하는 데 사용하는 조정 가능한 확장 지점에 대한 멋진 이름일 뿐입니다.
요약
Java 생태계, 특히 종속성 주입을 일반적으로 사용하는 많은 프레임워크에서 제어 반전이 널리 퍼져 있지만 이 모델은 대부분의 개발자에게 여전히 모호하며 적용도 종속성 주입으로 제한됩니다. 이 기사에서는 IoC를 구현하는 몇 가지 실용적인 방법을 보여줌으로써 이 개념을 설명합니다.
종속성 주입: 클라이언트로부터 종속성을 얻는 제어는 더 이상 이러한 클래스에 존재하지 않습니다. 이는 기본 인젝터/DI 프레임워크에 의해 처리됩니다.
관찰자 패턴: 주체가 변경되면 제어권이 관찰자에서 주체로 넘어갑니다.
템플릿 메소드 패턴: 알고리즘 단계를 구현하는 하위 클래스가 아닌 템플릿 메소드를 정의하는 기본 클래스에서 제어가 발생합니다.
언제나 IoC를 어떻게, 언제 사용할지는 각 사용 사례를 분석하여 결정됩니다. IoC를 위해 IoC를 수행하지 마세요.
위 내용은 Java가 IOC 제어 반전의 세 가지 디자인 패턴을 사용하는 방법에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!