Principles of Software Construction: Objects, Design, and Concurrency��Libraries and Frameworks
(Design for large-scale reuse)
Jeremy Lacomis and Christian Kaestner
1
17-214/514
Administrivia
2
17-214/514
Quiz
https://bit.ly/214Q13
3
17-214/514
Name three things that make designing distributed systems more challenging than designing a purely local system.
4
17-214/514
Suppose you are designing a system that has three components, A, B, and C such that A.foo() calls B.bar() which in turn calls C.baz(). To follow the best practices of modular protection, where is the best place for an exception that occurs inside of C.baz() to be handled?
5
17-214/514
Learning goals for today
6
17-214/514
Where Does That Get Us?
Subtype Polymorphism ✓
Information Hiding, Contracts ✓
Immutability ✓
Types ✓
Unit Testing ✓
Domain Analysis ✓
Inheritance & Del. ✓
Responsibility�Assignment,�Design Patterns,�Antipattern ✓
Promises/�Reactive P. ✓
Integration Testing ✓
GUI vs Core ✓
Frameworks and Libraries, APIs
Module systems,�microservices ✓
(Testing for) Robustness
CI ✓, DevOps, Teams
Design for
understanding
change/ext.
reuse
robustness
...
Small scale:
One/few objects
Mid scale:
Many objects
Large scale:
Subsystems
7
17-214/514
Earlier in this course: Class-level reuse
Language mechanisms supporting reuse
Design principles supporting reuse
Design patterns supporting reuse
* Effective Java items 26, 29, 30, and 31
8
17-214/514
Where Does That Get Us?
Subtype Polymorphism ✓
Information Hiding, Contracts ✓
Immutability ✓
Types ✓
Unit Testing ✓
Domain Analysis ✓
Inheritance & Del. ✓
Responsibility�Assignment,�Design Patterns,�Antipattern ✓
Promises/�Reactive P. ✓
Integration Testing ✓
GUI vs Core ✓
Frameworks and Libraries, APIs
Module systems,�microservices ✓
(Testing for) Robustness
CI ✓, DevOps, Teams
Design for
understanding
change/ext.
reuse
robustness
...
Small scale:
One/few objects
Mid scale:
Many objects
Large scale:
Subsystems
9
17-214/514
Today: Reuse at scale
10
17-214/514
Reuse and variation: �Family of development tools
11
17-214/514
Reuse and variation: �Visual Studio Code Extensions
12
17-214/514
Reuse and variation: Web browser extensions
13
17-214/514
Reuse and variation: �Flavors of Linux
14
17-214/514
Reuse and variation: �Product lines
15
17-214/514
Today: Reuse at scale
16
17-214/514
Terminology: Library
Library
public MyWidget extends JContainer {
public MyWidget(int param) {
// setup internals, without rendering
}
// render component on first view and resizing
protected void paintComponent(Graphics g) {
// draw a red box on his component
Dimension d = getSize();
g.setColor(Color.red);
g.drawRect(0, 0, d.getWidth(), d.getHeight());
}
}
your code
17
17-214/514
Terminology: Frameworks
Framework
public MyWidget extends JContainer {
public MyWidget(int param) {
// setup internals, without rendering
}
// render component on first view and resizing
protected void paintComponent(Graphics g) {
// draw a red box on his component
Dimension d = getSize();
g.setColor(Color.red);
g.drawRect(0, 0, d.getWidth(), d.getHeight());
}
}
your code
18
17-214/514
A calculator example (without a framework)
public class Calc extends JFrame {
private JTextField textField;
public Calc() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText("calculate");
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
textField.setText("10 / 2 + 6");
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textfield, BorderLayout.WEST);
button.addActionListener(/* calculation code */);
this.setContentPane(contentPane);
this.pack();
this.setLocation(100, 100);
this.setTitle("My Great Calculator");
...
}
}
19
17-214/514
A simple example framework
20
17-214/514
A calculator example (without a framework)
public class Calc extends JFrame {
private JTextField textField;
public Calc() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText("calculate");
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
textField.setText("10 / 2 + 6");
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textfield, BorderLayout.WEST);
button.addActionListener(/* calculation code */);
this.setContentPane(contentPane);
this.pack();
this.setLocation(100, 100);
this.setTitle("My Great Calculator");
...
}
}
21
17-214/514
A simple example framework
public abstract class Application extends JFrame {
protected String getApplicationTitle() { return ""; }
protected String getButtonText() { return ""; }
protected String getInitialText() { return ""; }
protected void buttonClicked() { }
private JTextField textField;
public Application() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText(getButtonText());
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
textField.setText(getInitialText());
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textField, BorderLayout.WEST);
button.addActionListener((e) -> { buttonClicked(); });
this.setContentPane(contentPane);
this.pack();
this.setLocation(100, 100);
this.setTitle(getApplicationTitle());
...
}
22
17-214/514
Using the example framework
public abstract class Application extends JFrame {
protected String getApplicationTitle() { return ""; }
protected String getButtonText() { return ""; }
protected String getInitialText() { return ""; }
protected void buttonClicked() { }
private JTextField textField;
public Application() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText(getButtonText());
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
textField.setText(getInitialText());
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textField, BorderLayout.WEST);
button.addActionListener((e) -> { buttonClicked(); });
this.setContentPane(contentPane);
this.pack();
this.setLocation(100, 100);
this.setTitle(getApplicationTitle());
...
}
public class Calculator extends Application {
protected String getApplicationTitle() { return "My Great Calculator"; }
protected String getButtonText() { return "calculate"; }
protected String getInititalText() { return "(10 – 3) * 6"; }
protected void buttonClicked() {
JOptionPane.showMessageDialog(this, "The result of " + getInput() +
" is " + calculate(getInput()));
}
private String calculate(String text) { ... }
}
23
17-214/514
Using the example framework again
public abstract class Application extends JFrame {
protected String getApplicationTitle() { return ""; }
protected String getButtonText() { return ""; }
protected String getInitialText() { return ""; }
protected void buttonClicked() { }
private JTextField textField;
public Application() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText(getButtonText());
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
textField.setText(getInitialText());
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textField, BorderLayout.WEST);
button.addActionListener((e) -> { buttonClicked(); });
this.setContentPane(contentPane);
this.pack();
this.setLocation(100, 100);
this.setTitle(getApplicationTitle());
...
}
public class Ping extends Application {
protected String getApplicationTitle() { return "Ping"; }
protected String getButtonText() { return "ping"; }
protected String getInititalText() { return "127.0.0.1"; }
protected void buttonClicked() { ... }
}
24
17-214/514
General distinction: Library vs. framework
Library
Framework
public MyWidget extends JContainer {
ublic MyWidget(int param) {/ setup internals, without rendering
}
/ render component on first view and resizing
protected void paintComponent(Graphics g) {
// draw a red box on his componentDimension d = getSize();
g.setColor(Color.red);
g.drawRect(0, 0, d.getWidth(), d.getHeight()); }
}
public MyWidget extends JContainer {
ublic MyWidget(int param) {/ setup internals, without rendering
}
/ render component on first view and resizing
protected void paintComponent(Graphics g) {
// draw a red box on his componentDimension d = getSize();
g.setColor(Color.red);
g.drawRect(0, 0, d.getWidth(), d.getHeight()); }
}
your code
user
interacts
your code
user
interacts
25
17-214/514
Libraries and frameworks in practice
framework library
framework
application
credit: Erich Gamma
26
17-214/514
Framework vs. Library
We already saw some examples of frameworks:
What about these?
27
17-214/514
Is Express a framework or a library?
ⓘ
Click Present with Slido or install our Chrome extension to activate this poll while presenting.
28
17-214/514
Is Handlebars a framework or a library?
ⓘ
Click Present with Slido or install our Chrome extension to activate this poll while presenting.
29
17-214/514
Is Santorini a Framework?
30
17-214/514
More terms
31
17-214/514
More terms
32
17-214/514
Today: Libraries and frameworks for reuse
33
17-214/514
WHITE-BOX VS BLACK-BOX* FRAMEWORKS
* outdated terms, not aware of common replacements; maybe Inheritance-Based vs Delegation-Based Frameworks
34
17-214/514
Whitebox (inheritance-based) frameworks
35
17-214/514
Blackbox (delegation-based) frameworks
36
17-214/514
Is this a whitebox or blackbox framework?
public abstract class Application extends JFrame {
protected String getApplicationTitle() { return ""; }
protected String getButtonText() { return ""; }
protected String getInitialText() { return ""; }
protected void buttonClicked() { }
private JTextField textField;
public Application() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText(getButtonText());
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
textField.setText(getInitialText());
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textField, BorderLayout.WEST);
button.addActionListener((e) -> { buttonClicked(); });
this.setContentPane(contentPane);
this.pack();
this.setLocation(100, 100);
this.setTitle(getApplicationTitle());
...
}
public class Calculator extends Application {
protected String getApplicationTitle() { return "My Great Calculator"; }
protected String getButtonText() { return "calculate"; }
protected String getInititalText() { return "(10 – 3) * 6"; }
protected void buttonClicked() {
JOptionPane.showMessageDialog(this, "The result of " + getInput() +
" is " + calculate(getInput()));
}
private String calculate(String text) { ... }
}
public class Ping extends Application {
protected String getApplicationTitle() { return "Ping"; }
protected String getButtonText() { return "ping"; }
protected String getInititalText() { return "127.0.0.1"; }
protected void buttonClicked() { ... }
}
37
17-214/514
An example blackbox framework
public class Application extends JFrame {
private JTextField textField;
private Plugin plugin;
public Application() { }
protected void init(Plugin p) {
p.setApplication(this);
this.plugin = p;
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText(plugin != null ? plugin.getButtonText() : "ok");
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
if (plugin != null) textField.setText(plugin.getInititalText());
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textField, BorderLayout.WEST);
if (plugin != null)
button.addActionListener((e) -> { plugin.buttonClicked(); } );
this.setContentPane(contentPane);
...
}
public String getInput() { return textField.getText(); }
}
public interface Plugin {
String getApplicationTitle();
String getButtonText();
String getInititalText();
void buttonClicked() ;
void setApplication(Application app);
}
38
17-214/514
An example blackbox framework
public class Application extends JFrame {
private JTextField textField;
private Plugin plugin;
public Application() { }
protected void init(Plugin p) {
p.setApplication(this);
this.plugin = p;
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText(plugin != null ? plugin.getButtonText() : "ok");
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
if (plugin != null) textField.setText(plugin.getInititalText());
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textField, BorderLayout.WEST);
if (plugin != null)
button.addActionListener((e) -> { plugin.buttonClicked(); } );
this.setContentPane(contentPane);
...
}
public String getInput() { return textField.getText(); }
}
public interface Plugin {
String getApplicationTitle();
String getButtonText();
String getInititalText();
void buttonClicked() ;
void setApplication(Application app);
}
public class CalcPlugin implements Plugin {
private Application app;
public void setApplication(Application app) { this.app = app; }
public String getButtonText() { return "calculate"; }
public String getInititalText() { return "10 / 2 + 6"; }
public void buttonClicked() {
JOptionPane.showMessageDialog(null, "The result of "
+ app.getInput() + " is "
+ calculate(app.getInput()));
}
public String getApplicationTitle() { return "My Great Calculator"; }
}
39
17-214/514
An aside: Plugins could be reusable too…
public class Application extends JFrame implements InputProvider {
private JTextField textField;
private Plugin plugin;
public Application() { }
protected void init(Plugin p) {
p.setApplication(this);
this.plugin = p;
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
button.setText(plugin != null ? plugin.getButtonText() : "ok");
contentPane.add(button, BorderLayout.EAST);
textField = new JTextField("");
if (plugin != null) textField.setText(plugin.getInititalText());
textField.setPreferredSize(new Dimension(200, 20));
contentPane.add(textField, BorderLayout.WEST);
if (plugin != null)
button.addActionListener((e) -> { plugin.buttonClicked(); } );
this.setContentPane(contentPane);
...
}
public String getInput() { return textField.getText(); }
}
public interface Plugin {
String getApplicationTitle();
String getButtonText();
String getInititalText();
void buttonClicked() ;
void setApplication(InputProvider app);
}
public class CalcPlugin implements Plugin {
private InputProvider app;
public void setApplication(InputProvider app) { this.app = app; }
public String getButtonText() { return "calculate"; }
public String getInititalText() { return "10 / 2 + 6"; }
public void buttonClicked() {
JOptionPane.showMessageDialog(null, "The result of "
+ app.getInput() + " is "
+ calculate(app.getInput()));
}
public String getApplicationTitle() { return "My Great Calculator"; }
}
public interface InputProvider {
String getInput();
}
40
17-214/514
Frameworks summary
41
17-214/514
Framework design considerations
42
17-214/514
USE VS REUSE:�DOMAIN ENGINEERING
43
17-214/514
44
17-214/514
(one modularization: tangrams)
45
17-214/514
Tangrams
46
17-214/514
The use vs. reuse dilemma
“maximizing reuse minimizes use”
C. Szyperski
47
17-214/514
Domain engineering
48
17-214/514
The cost of changing a framework
public class Application extends JFrame {
private JTextField textfield;
private Plugin plugin;
public Application(Plugin p) { this.plugin=p; p.setApplication(this); init(); }
protected void init() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
if (plugin != null)
button.setText(plugin.getButtonText());
else
button.setText("ok");
contentPane.add(button, BorderLayout.EAST);
textfield = new JTextField("");
if (plugin != null)
textfield.setText(plugin.getInititalText());
textfield.setPreferredSize(new Dimension(200, 20));
contentPane.add(textfield, BorderLayout.WEST);
if (plugin != null)
button.addActionListener(/* … plugin.buttonClicked();… */);
this.setContentPane(contentPane);
…
}
public String getInput() { return textfield.getText();}
}
public class CalcPlugin implements Plugin {
private Application application;
public void setApplication(Application app) { this.application = app; }
public String getButtonText() { return "calculate"; }
public String getInititalText() { return "10 / 2 + 6"; }
public void buttonClicked() {
JOptionPane.showMessageDialog(null, "The result of "
+ application.getInput() + " is "
+ calculate(application.getText())); }
public String getApplicationTitle() { return "My Great Calculator"; }
}
public interface Plugin {
String getApplicationTitle();
String getButtonText();
String getInititalText();
void buttonClicked() ;
void setApplication(Application app);
}
49
17-214/514
The cost of changing a framework
public class Application extends JFrame {
private JTextField textfield;
private Plugin plugin;
public Application(Plugin p) { this.plugin=p; p.setApplication(this); init(); }
protected void init() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(new BevelBorder(BevelBorder.LOWERED));
JButton button = new JButton();
if (plugin != null)
button.setText(plugin.getButtonText());
else
button.setText("ok");
contentPane.add(button, BorderLayout.EAST);
textfield = new JTextField("");
if (plugin != null)
textfield.setText(plugin.getInititalText());
textfield.setPreferredSize(new Dimension(200, 20));
contentPane.add(textfield, BorderLayout.WEST);
if (plugin != null)
button.addActionListener(/* … plugin.buttonClicked();… */);
this.setContentPane(contentPane);
…
}
public String getInput() { return textfield.getText();}
}
public class CalcPlugin implements Plugin {
private Application application;
public void setApplication(Application app) { this.application = app; }
public String getButtonText() { return "calculate"; }
public String getInititalText() { return "10 / 2 + 6"; }
public void buttonClicked() {
JOptionPane.showMessageDialog(null, "The result of "
+ application.getInput() + " is "
+ calculate(application.getText())); }
public String getApplicationTitle() { return "My Great Calculator"; }
}
public interface Plugin {
String getApplicationTitle();
String getButtonText();
String getInititalText();
void buttonClicked() ;
void setApplication(Application app);
}
Consider adding an extra method.
50
17-214/514
Learning a framework
effort
reward
Library
Framework
51
17-214/514
Typical framework design and implementation
Define your domain
Identify potential common parts and variable parts
Design and write sample plugins/applications
Factor out & implement common parts as framework
Provide plugin interface & callback mechanisms for variable parts
Use well-known design principles and patterns where appropriate…
Get lots of feedback, and iterate
52
17-214/514
FRAMEWORK MECHANICS
53
53
17-214/514
Running a framework
54
17-214/514
Methods to load plugins
1. Client writes main function, creates a plugin object, and passes it to framework
(see blackbox example above)
2. Framework has main function, client passes name of plugin as a command line argument or environment variable
(see next slide)
3. Framework looks in a magic location
Config files or .jar/.js files in a plugins/ directory are automatically loaded and processed
4. GUI for plugin management
E.g., web browser extensions
55
17-214/514
An example plugin loader using Java Reflection
public static void main(String[] args) {
if (args.length != 1)
System.out.println("Plugin name not specified");
else {
String pluginName = args[0];
try {
Class<?> pluginClass = Class.forName(pluginName);
new Application((Plugin) pluginClass.newInstance()).setVisible(true);
} catch (Exception e) {
System.out.println("Cannot load plugin " + pluginName
+ ", reason: " + e);
}
}
}
56
17-214/514
An example plugin loader in Node.js
const args = process.argv
if (args.length < 3)
console.log("Plugin name not specified");
else {
const plugin = require("plugins/"+args[2]+".js")()
startApplication(plugin)
}
57
17-214/514
Another plugin loader using Java Reflection
public static void main(String[] args) {
File config = new File(".config");
BufferedReader reader = new BufferedReader(new FileReader(config));
Application = new Application();
Line line = null;
while ((line = reader.readLine()) != null) {
try {
Class<?> pluginClass = Class.forName(line);
application.addPlugin((Plugin) pluginClass.newInstance());
} catch (Exception e) {
System.out.println("Cannot load plugin " + line
+ ", reason: " + e);
}
}
reader.close();
application.setVisible(true);
}
58
17-214/514
GUI-based plugin management
59
17-214/514
Supporting multiple plugins
public class Application {
private List<Plugin> plugins;
public Application(List<Plugin> plugins) {
this.plugins=plugins;
for (Plugin plugin: plugins)
plugin.setApplication(this);
}
public Message processMsg (Message msg) {
for (Plugin plugin: plugins)
msg = plugin.process(msg);
...
return msg;
}
}
60
17-214/514
Example: A VS Code Extension
61
17-214/514
Example: A JUnit Plugin
public class SampleTest {
private List<String> emptyList;
@Before
public void setUp() {
emptyList = new ArrayList<String>();
}
@After
public void tearDown() {
emptyList = null;
}
@Test
public void testEmptyList() {
assertEquals("Empty list should have 0 elements",
0, emptyList.size());
}
}
Here the important plugin
mechanism is Java
annotations
62
17-214/514
Summary
63
17-214/514