Software Craftsmanship
OOP Crash Course
September 18, 2016
Object Oriented Programming is all about encapsulating moving parts into self-sustainable objects.
There are 4 core OOP concepts
We are going to focus on the first three in this lecture.
Encapsulation is a simple concept; it's the black box that contains all of the internal implementation details. There are multiple scopes that can be encapsulated but for this class we will focus on class level encapsulation.
What are the primary goals of encapsulation?
Is the a good or bad example of encapsulation?
public class Point1 {
public double x;
public double y;
}
Remember the primary goals of Encapsulation are.
Is the a good or bad example of encapsulation?
public class Point2 {
double getX();
double getY();
void setLocation(double x, double y);
}
Is the a good or bad example of encapsulation?
public interface Point3 {
double getX();
double getY();
void setCartesian(double x, double y);
double getR();
double getTheta();
void setPolar(double radius, double theta);
}
You can go from example 2 to example 3 without performing "Shotgun Surgery".
What is an association?
An association is simply a reference to another object.
class Class1 {
private Class2 class2Instance;
public Class1() {
this.class2Instance = Class2();
}
}Aggregation
Aggregation is when Class A has a reference to Object B and Object B is able to exist outside Class A.
Composition
Composition is when Class A has a reference to Object B, but Object B is tied to the life cycle of Class A.
Composition or Aggregation?
class Customer
{
private Account account;
public Customer()
{
this.account = new Account();
}
}Composition
Composition or Aggregation?
class Customer
{
private Account account;
public Customer(Account account)
{
this.account = account;
}
}Aggregation
public class DoubleLinkedList
{
public synchronized void addLast(T me)
{
if ( first == null )
{
// empty list.
first = me;
}
else
{
last.next = me;
me.prev = last;
}
last = me;
size++;
}
}public class DoubleLinkedListNode
{
private final T payload;
/** Double Linked list references */
public DoubleLinkedListNode<T> prev;
/** Double Linked list references */
public DoubleLinkedListNode<T> next;
public DoubleLinkedListNode(T payloadP)
{
payload = payloadP;
}
public T getPayload()
{
return payload;
}
}Aggregation provides references to objects you have no control over.
What are the pitfalls of aggregation?
When is aggregation appropriate?
Composition provides references to objects you have complete control over.
What are the pitfalls of composition?
When is composition appropriate?
Does composition introduce coupling?
Abstraction is a technique for arranging complexity of computer systems. It works by establishing a level of complexity on which a person interacts with the system, suppressing the more complex details below the current level. - Wikipedia
How is Abstraction different from Encapsulation?
public abstract class LoggerBase {
protected LoggerBase() {
logger = log4net.LogManager.GetLogger(this.LogPrefix);
log4net.Config.DOMConfigurator.Configure();
}
protected void LogError(string message) {
if (this.logger.IsErrorEnabled) {
this.logger.Error(message);
}
}
}public interface ILogger
{
bool LogError(string message)
}What are the differences?
When would you prefer one over the other?
To summarize, abstract classes are for creating extensible systems. Interfaces are for ensuring behaviors.
public class LRUMap<K, V> extends AbstractLinkedMap<K, V>
implements BoundedMap<K, V>, Serializable, Cloneable {
/**
* A <code>Map</code> implementation with a fixed maximum size which removes
* the least recently used entry if an entry is added when full.
*/
}
public abstract class AbstractLinkedMap<K, V>
extends AbstractHashedMap<K, V> implements OrderedMap<K, V> {
}
public interface BoundedMap<K, V> extends IterableMap<K, V> {
boolean isFull();
int maxSize();
}
public interface OrderedMap<K, V> extends IterableMap<K, V> {
boolean firstKey();
int lastKey();
K nextKey(K key);
K previousKey(K key);
}
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
//Can require a parameter to extend multiple protocols
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
public interface A {
default void foo(){
System.out.println("Calling A.foo()");
}
}
public class Clazz implements A {
}
https://github.kdc.capitalone.com/gist/kat269/0f15e3dae3c15948aa9c5023f3974fda
And remember!
Often times people will use aggregation to reduce boiler plate and prevent continually passing around variables.
class Request {
public Dispatcher dispatcher;
public Request(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
public send() {
this.dispatcher.send(this.headers, this.body, this.timeout);
}
}class Request {
public send(final Dispatcher dispatcher) {
dispatcher.send(this.headers, this.body, this.timeout);
}
}Pros/Cons?
With example 2, all interactions of note are visible at a glance.
Example 2 is deterministic
Imperative Programming
"In computer science, imperative programming is a programming paradigm that uses statements that change a program's state. In much the same way that the imperative mood in natural languages expresses commands, an imperative program consists of commands for the computer to perform." - Wikipedia
Functional Programming
"In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data." - Wikipedia
Pros/Cons?
(According to Microsoft)
Increased readability and maintainability. This is because each function is designed to accomplish a specific task given its arguments. The function does not rely on any external state.
Easier reiterative development. Because the code is easier to refactor, changes to design are often easier to implement. For example, suppose you write a complicated transformation, and then realize that some code is repeated several times in the transformation. If you refactor through a pure method, you can call your pure method at will without worrying about side effects.
Easier testing and debugging. Because pure functions can more easily be tested in isolation, you can write test code that calls the pure function with typical values, valid edge cases, and invalid edge cases.
A large fraction of the flaws in software development are due to programmers not fully understanding all the possible states their code may execute in. ... Programming in a functional style makes the state presented to your code explicit, which makes it much easier to reason about.
Reusability. It is much easier to transplant a pure function to a new environment. ... How many times have you known there was some code that does what you need in another system, but extricating it from all of its environmental assumptions was more work than just writing it over?
Easier testing and debugging. Because pure functions can more easily be tested in isolation, you can write test code that calls the pure function with typical values, valid edge cases, and invalid edge cases.
Functions have to abide by two rules
Is it possible for all functions to be functional?
A program that never alters state is probably not doing anything worth doing. Databases, file systems, etc... are never going to be deterministic. This is fine, the functional ideal is to compose stateless data capable of describing the transformation and then applying it all at once.
Imperative Collection
list = NewList();
list.Add(1)
list.Add(3)
list.Add(5)
list.Add(7)Functional Collection
list = NewList();
list = list.add(1)
list = list.Add(3)
list.Add(5)
list = list.Add(7)Results [1,3,5,7]. The state of list changes each time add is called.
Results [1,3,7]. The list is overwritten each time the internal state never changes.
public class Program {
private static string aMember = "StringOne";
public static void HypenatedConcat(string appendStr) {
aMember += '-' + appendStr;
}
public static void Main() {
HypenatedConcat("StringTwo");
Console.WriteLine(aMember); //StringOne-StringTwo
HypenatedConcat("StringTwo");
Console.WriteLine(aMember); //StringOne-StringTwo-StringTwo
}
}Is HypenatedConcat "Pure"?
No: HypenatedConcat will never execute the same way twice. This example is especially bad because multiple libraries in the same process space will be altering the aMember variable.
public class Program {
public static void HypenatedConcat(StringBuilder sb, String appendStr) {
sb.Append('-' + appendStr);
}
public static void Main() {
StringBuilder sb1 = new StringBuilder("StringOne");
HypenatedConcat(sb1, "StringTwo");
Console.WriteLine(sb1); //StringOne-StringTwo
HypenatedConcat(sb1, "StringTwo");
Console.WriteLine(sb1); //StringOne-StringTwo-StringTwo
}
}Is HypenatedConcat "Pure"?
No: StringBuilder sb is changed in main
Imperative HTTP Request (Java Jersey Jackson Client)
HTTPBasicAuthFilter filter = new HTTPBasicAuthFilter("user", "password");
//Client is immutable. ClientBuilder returns clones of its self after each function call
Client client = ClientBuilder.newBuilder()
.register(JacksonFeature.class)
.addFilter(filter)
.build();
//WebTarget returns clones of its self after each function call
WebTarget target = client.target("http://localhost:8080").path("resource");
//The form is the only thing here that is mutable
Form form = new Form();
form.param("x", "foo");
form.param("y", "bar");
Response response = target.request(MediaType.APPLICATION_JSON_TYPE)
.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), Response.class);
Map<String, Object> jsonResponse = response(Map.class);Functional programs strive to be data driven vs object driven.
Was the previous example class or data driven?
(client/post "http://localhost:8080"
{:basic-auth ["user" "password"]
:form-params
{:x "foo"
:y "bar"}
:as :json})Data driven Clojure Example
Many modern OOP libraries are increasingly data driven.
Map<String, String> data = new HashMap<String, String>();
data.put("x", "foo");
data.put("y", "bar");
String response = post("http://localhost:8080")
.basic("user", "password")
.form(data)
.body();
JSONObject obj = new JSONObject(response);