[![enter image description here][1]][1]
You can call it ports and adapters or hexagonal architecture. Regardles what's facinated me about this picture isn't the layers. It's the UML diagram in the corner that looks like a folded over version of the Dependency Inversion Principle:
[![enter image description here][2]][2]
Some like to think of an interface like it's owned by the class that implements it.
Some like to think of an interface like it's owned by the client that uses it.
For the first time I'm starting to think either can be true. Both interfaces are really owned by the inner layer.
I've written some example code that is just suposed to show how these layer plugins might look. Please check it for clarity.
Ordered to follow the flow of control:
package candiedOrange.plugin.adapters;
import candiedOrange.plugin.usecases.ButtonUseCaseInputPort;
public class ButtonControler {
ButtonUseCaseInputPort button;
public ButtonControler(ButtonUseCaseInputPort button) {
this.button = button;
}
public void push() {
button.push();
}
}
package candiedOrange.plugin.usecases;
public interface ButtonUseCaseInputPort {
void push();
}
package candiedOrange.plugin.usecases;
public class ButtonPushUseCaseInteractor implements ButtonUseCaseInputPort {
ButtonUseCaseOutputPort outputPort;
public ButtonPushUseCaseInteractor(ButtonUseCaseOutputPort outputPort){
this.outputPort = outputPort;
}
@Override
public void push() {
outputPort.push();
}
}
package candiedOrange.plugin.usecases;
public interface ButtonUseCaseOutputPort {
void push();
}
package candiedOrange.plugin.adapters;
import candiedOrange.plugin.usecases.ButtonUseCaseOutputPort;
public class ButtonPresenter implements ButtonUseCaseOutputPort{
@Override
public void push() {
System.out.print("push");
}
}
And some tests:
package candiedOrange.plugin;
import candiedOrange.plugin.adapters.ButtonControler;
import candiedOrange.plugin.adapters.ButtonPresenter;
import candiedOrange.plugin.usecases.ButtonPushUseCaseInteractor;
import candiedOrange.plugin.usecases.ButtonUseCaseInputPort;
import candiedOrange.plugin.usecases.ButtonUseCaseOutputPort;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class TestCleanArchitecture {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private PrintStream oldStdOut;
@Before
public void setUpStreams() {
oldStdOut = System.out;
System.setOut(new PrintStream(outContent));
}
@After
public void cleanUpStreams() {
System.setOut(oldStdOut);
}
@Test
public void testOut() {
System.out.print("hello");
assertEquals("hello", outContent.toString());
}
@Test
public void testPresenter() {
outContent.reset();
new ButtonPresenter().push();
assertEquals("push", outContent.toString());
}
static class PushMock implements ButtonUseCaseInputPort, ButtonUseCaseOutputPort{
boolean pushed;
public boolean isPushed() { return pushed; }
public void push() { this.pushed = true; }
}
@Test
public void testInteractor() {
PushMock presentorMock = new PushMock();
assertFalse(presentorMock.isPushed());
new ButtonPushUseCaseInteractor(presentorMock).push();
assertTrue(presentorMock.isPushed());
}
@Test
public void testControler() {
PushMock interactorMock = new PushMock();
assertFalse(interactorMock.isPushed());
new ButtonControler(interactorMock).push();
assertTrue(interactorMock.isPushed());
}
@Test
public void testEndToEnd() {
outContent.reset();
new ButtonControler(new ButtonPushUseCaseInteractor(new ButtonPresenter())).push();
assertEquals("push", outContent.toString());
}
}
Package structure:
[![enter image description here][3]][3]
Looking to make the above into a readable example. Critical input welcome.
[1]: https://i.sstatic.net/uhHZ5.jpg
[2]: https://i.sstatic.net/VrV8b.png
[3]: https://i.sstatic.net/EvyGU.png