-
Dependency Inversion Principle (DIP)Modeling/DesignPattern 2020. 2. 28. 11:16
1. Overview
High-level modules should not depend upon low-level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.
2. Description
2.1 Dependency
public void printMe() { System.out.println("Hello"); }
printMe() method depends on out object defined in System Class.
public void writeReport() { Report report = new Report(); // Build the report JSONFormatter formatter = new JSONFormatter(); String report = formatter.format(report); FilerWriter writer = new FileWriter("report.json"); // write out report }
formatter and writer are dependencies. We are instantiating these objects in our method. That's why we are tightly coupling our reporter generation to these particular implementations.
What if somebody says you know you're writing all of this. That is not really good. We want to send that report to a different server. Why don't you just post whatever report that you generate post that whole string to this particular? What are you going to do? You are going to modify your existing board generation method.
2.2 Dependency Inversion
Dependency was in principle is telling us that instead of tightly coupling our high-level module basically means a module that provides or that implements some business rules.
What we write a low-level module is basically a functionality that is so basic that it can be used anywhere, for example, writing to disk is something that is that can be used anywhere converting Java object to Jason.
So our high-level module should not depend on low-level modules. And what is telling us each tells us that both should depend on abstraction.
So instead of creating a new instance of you know object mapper or creating a new instance of file writer, we can use an interface and we will write our high-level module code using that interface.
public void writeReport(Formatter formatter, Writer writer) { Report report = new Report(); // Build the report String report = formatter.format(report); // write out report writer.write("myreport"); }
So we're in sort of creating a new object in every method, We will accept parameters of these particular interfaces. The benefit is that our code is no longer tightly coupled to any concrete glass anybody who wants to generate a report let's say he wants an estimate report.
3. Example
3.1 Violate DIP
interface Formatter { public String format(Message message) throws FormatException; } class FormatException extends IOException { public FormatException(Exception cause) { super(cause); } } class TextFormatter implements Formatter{ public String format(Message message){ return message.getTimestamp()+":"+message.getMsg(); } } class JSONFormatter implements Formatter{ public String format(Message message) throws FormatException { ObjectMapper mapper = new ObjectMapper(); try { return mapper.writeValueAsString(message); } catch (JsonProcessingException e) { e.printStackTrace(); throw new FormatException(e); } } } class Message { private String msg; private LocalDateTime timestamp; public Message(String msg) { this.msg = msg; this.timestamp = LocalDateTime.now(ZoneId.of("UTC")); } public String getMsg() { return msg; } public LocalDateTime getTimestamp() { return timestamp; } } class MessagePrinter { public void writeMessage(Message msg, String fileName) throws IOException { Formatter formatter = new JSONFormatter(); try(PrintWriter writer = new PrintWriter(new FileWriter(fileName))) { //creates print writer writer.println(formatter.format(msg)); //formats and writes message writer.flush(); writer.close(); } } } public class Demo { public static void main(String[] args) throws IOException { Message msg = new Message("This is a message again"); MessagePrinter printer = new MessagePrinter(); printer.writeMessage(msg, "./test_msg.txt"); } }
If we want to print this message on console then the current method is not going to work unless we go and write another method there instead of using print writer instead of system writer.
The second problem is that if we want to change the format of the message then we have to again modify this particular method and change the JSON format or to the new class that we want to use to format our message object.
3.2 Follow DIP
interface Formatter { public String format(Message message) throws FormatException; } class FormatException extends IOException { public FormatException(Exception cause) { super(cause); } } class TextFormatter implements Formatter{ public String format(Message message){ return message.getTimestamp()+":"+message.getMsg(); } } class JSONFormatter implements Formatter{ public String format(Message message) throws FormatException { ObjectMapper mapper = new ObjectMapper(); try { return mapper.writeValueAsString(message); } catch (JsonProcessingException e) { e.printStackTrace(); throw new FormatException(e); } } } class Message { private String msg; private LocalDateTime timestamp; public Message(String msg) { this.msg = msg; this.timestamp = LocalDateTime.now(ZoneId.of("UTC")); } public String getMsg() { return msg; } public LocalDateTime getTimestamp() { return timestamp; } } class MessagePrinter { public void writeMessage(Message msg, Formatter formatter, PrintWriter writer) throws IOException { // only business logic here writer.println(formatter.format(msg)); //formats and writes message writer.flush(); } } public class Demo { public static void main(String[] args) throws IOException { Message msg = new Message("This is a message again"); MessagePrinter printer = new MessagePrinter(); // printer.writeMessage(msg, new JSONFormatter(), new PrintWriter(new FileWriter("test_msg.txt"))); printer.writeMessage(msg, new JSONFormatter(), new PrintWriter(System.out)); } }
4. Reference
https://en.wikipedia.org/wiki/Dependency_inversion_principle
'Modeling > DesignPattern' 카테고리의 다른 글
Command (0) 2020.02.29 Flyweight (0) 2020.02.28 Interface Segregation Principle (ISP) (0) 2020.02.28 Liskov Substitution Principle (LSP) (0) 2020.02.28 Open-Closed Principle (OCP) (0) 2020.02.26