Eric Chapdelaine
Student at Northeastern University Studying Computer Science.
Notes
Topics
Projects
Articles
Resume

Email
GitHub
LinkedIn

CS3500 Object Oriented Design


The Class

Everything will be on Canvas

Self-evals are worth about 2-3 percent (each) of your grade

Exam is on June 1 (in 3 weeks). Exam 2 is two weeks after Exam 1.

First 4 assignments are completed by yourself. The next four are with partners.

2 late days for the first 4 assignments. Another 2 late days for the rest.

We will work with Java until Exam 2. Then C/C++.

If you are going to read a book, read Head First Design Patterns: A Brain-Friendly Guide.

We will be learning design patterns, not Java concepts:

How do we write code so that we can introduce change (without having to modify the code)?

What we won’t learn:

Topics

S Single Responsibility
O Open to extension, closed to modification
L Liskov substitution
I Interface segregation
D Dependency inversion

Why learn SOLID and design patterns?

Unlike other fields, the design process for software engineering is more like:

  1. Cursory analysis
  2. Completely wrong implementation
  3. More cursory analysis
  4. Wrong-headed design
  5. Some implementation and testing
  6. More analysis and re-design
  7. More implementation and testing
  8. Iterate, iterate, iterate
  9. Deployment
  10. Bug reports
  11. Head scratching
  12. Coffee
  13. Temptation to rewrite from scratch

How do we break up code into individual classes that can interact with each other such that it will enable us to introduce change without having to modify the design extensively?

Other topics we will cover:

What to remember while in this class:

Introduction to Object Oriented Design

Citation of publications (either Books or Articles)

Consider the implementation in Racket:

;; A Publication is one of:
;; -- (make-book String String String String Number)
;; -- (make-article String String String Number Number Number)
(define-struct book [title author publisher location year])
(define-struct article [title author journal volume issue year])

;; Examples:
(define rushdie.v1
        (make-book "Midnight's Children" "Salman Rushdie"
                   "Jonathan Cape" "London" 1980))
(define turing.v1
        (make-article "Computing machinery and intelligence"
                      "A. M. Turing" "Mind" 59 236 1950))

Then we can make our citation functions:

;; cite-apa: Publication -> String
;; To format a publication for citation in APA style.
(define (cite-apa pub)
  (cond
    [(book? pub)
     (format "~a (~a). ~a. ~a: ~a."
             (book-author pub) (book-year pub) (book-title pub)
             (book-location pub) (book-publisher pub))]
    [(article? pub)
     (format "~a (~a). ~a. ~a, ~a(~a)."
             (article-author pub) (article-year pub) (article-title pub)
             (article-journal pub) (article-volume pub) (article-issue pub))]))

;; cite-mla: Publication -> String
;; To format a publication for citation in MLA style.
(define (cite-mla pub)
  (cond
    [(book? pub)
     (format "~a. ~a. ~a: ~a, ~a."
             (book-author pub) (book-title pub) (book-location pub)
             (book-publisher pub) (book-year pub))]
    [(article? pub)
     (format "~a. \"~a.\" ~a ~a.~a (~a)."
             (article-author pub) (article-title pub) (article-journal pub)
             (article-volume pub) (article-issue pub) (article-year pub))]))

In an OO approach, the data would know how to cite itself. But in functional languages, we give the data to the function.

Now, what if we want to have a new publication: a webpage. How can we do this?

;; In APA function
 [(webpage? pub)
     (format "~a. Retrieved ~a, from ~a."
             (webpage-title pub) (webpage-retrieved pub) (webpage-url pub))]
             
;; In MLA function
[(webpage? pub)
    (format "\"~a.\" Web. ~a <~a>."
            (webpage-title pub) (webpage-retrieved pub) (webpage-url pub))]

In this, we need to modify the code. This is fine for now, because the change is small, but we usually want to avoid changing code because it can break things. Or sometimes, you won’t have permission to change the code.

Lets do an OO approach (ie the object knows how to cite itself) in Racket. We want to make the publications lambdas.

;; new-book: String String String String Number -> Publication
;; To construct a new book.
(define (new-book title author publisher location year)
  (lambda (style)
    (cond
      [(string=? style "apa")
       (format "~a (~a). ~a. ~a: ~a."
               author year title location publisher)]
      [(string=? style "mla")
       (format "~a. ~a. ~a: ~a, ~a."
               author title location publisher year)])))

;; Example:
(define rushdie.v2
        (new-book "Midnight's Children" "Salman Rushdie"
                  "Jonathan Cape" "London" 1980))

This is now how we cite:

(check-expect
 (rushdie.v2 "apa")
 "Salman Rushdie (1980). Midnight's Children. London: Jonathan Cape.")

Now adding the webpage, we don’t need to change the code. But if we were to add a new citation style, we would (so advantages and disadvantages).

How do we do this in Java?

/**
 * Specifies operations for formatting citations from bibliographic data.
 */
public interface Publication {
  /**
   * Formats a citation in APA style.
   *
   * @return the formatted citation
   */
  String citeApa();

  /**
   * Formats a citation in MLA style.
   *
   * @return the formatted citation
   */
  String citeMla();
}

Book’s implementation:

/**
 * The {@code Book} class represents bibliographic information for books.
 */
public class Book implements Publication {
  private final String title, author, publisher, location;
  private final int year;
  
    /** Constructs a {@code Book} object.
   *
   * @param title     the title of the book
   * @param author    the author of the book
   * @param publisher the publisher of the book
   * @param location  the location of the publisher
   * @param year      the year of publication
   */
  public Book(String title, String author, String publisher,
              String location, int year)
  {
    this.title = title;
    this.author = author;
    this.publisher = publisher;
    this.location = location;
    this.year = year;
  }
  
    public String citeApa() {
    return author + " (" + year + "). " + title + ". "
             + location + ": " + publisher + ".";
  }

  public String citeMla() {
    return author + ". " + title + ". " + location + ": "
             + publisher + ", " + year + ".";
  }
}


The Article’s implementation is similar as well.

Testing (JUnit):

Example with Book implementation above:

import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class BookTest {
  Publication rushdie = new Book("Midnight's Children", "Salman Rushdie",
                                 "Jonathan Cape", "London", 1980);
  @Test
  public void testCiteApa() {
    assertEquals(
      "Salman Rushdie (1980). Midnight's Children. London: Jonathan Cape.",
      rushdie.citeApa());
  }

  @Test
  public void testCiteMla() {
    assertEquals(
      "Salman Rushdie. Midnight's Children. London: Jonathan Cape, 1980.",
      rushdie.citeMla());
  }

}

In HandIn, your tests will be tested against bad code to see if it catches the errors.

Abstracting Tests

We want to abstract this:

private IDuration duration_impl_1_1_1;
private IDuration compact_duration_1_1_1;

@Test
public void inSecondsDurationImpl() {
    assertEquals(3661, duration_impl_1_1_1.inSeconds());
}

@Test
public void inSecondsCompactDuration() {
    assertEquals(3661, compact_duration_1_1_1.inSeconds());
}

Abstraction:

public abstract class AbstractDurationTest {
    private IDuration duration_1_1_1;

    protected abstract IDuration makeDurationFromSeconds(long totalSeconds);

    @Test
    public void testInSeconds() {
        assertEquals(3661, duration_1_1_1.inSeconds());
    }
}
public class DurationImplTest extends AbstractDurationTest {
    
    @Override
    protected Duration makeDurationFromSeconds(long totalSeconds) {
        return new CompactDurationImpl(totalSeconds);
    }
}

public class CompactDurationImplTest extends AbstractDurationTest {
    
    @Override
    protected Duration makeDurationFroMSeconds(long totalSeconds) {
        return new CompactDurationImpl(totalSeconds);
    }
}

Submitting Homework

Zip the src and test and submit the .zip in Handin.

JavaDoc

Compiles into HTML so you can put your own HTML in the JavaDoc.

/**
* formatHelper helps the format method to do XYZ
* @param start the index of the start of the format template
* @param end the index of the end of the format template
* @return some string.
*/
public String formatHelper(int start, int end) {
    return "";
}

Java Review

We need to determine:

A duration can be something like

Don’t focus just on the data. That can limit your implementation. Instead, think about the possible operations such as: (design your interface on this)

Make some assumptions:

What methods we need

The interface:

/**
 * Durations, with a minimum resolution of seconds. All durations are non-negative.
 * Different implementations should all work together using equals, hashcode, and compareTo:
 * <ul>
 *  <li> Two durations must be equal i they have the same number of seconds, they should be compared
 * by calling inSeconds();
 *  </li>
 *  <li> the hashCode of a duration in the result of calling {@link Long#hashCode(long)} and passing
 * the length in seconds that is by calling inSeconds
 *  </li>
 *  <li> compareTo should be using {@link Long#compare(long, long)} and pass in the total number
 * of seconds from the two durations 
 *  </li>
 * </ul>
 */
public interface IDuration extends Comparable<IDuration  {
  /**
   * Gets the total duration in seconds.
   *
   * @return the number of seconds (non-negative)
   */
  long inSeconds();

  /**
   * Formats this duration in the form {@code H:MM:SS} where the hours and
   * minutes are both zero-padded to two digits, but the hours are not.
   *
   * @return this duration formatted in hours, minutes, and seconds
   */
  String asHms();

  /**
   * Returns the sum of two durations.
   *
   * @param other the duration to add to {@code this}
   * @return the sum of the durations
   */
  IDuration plus(IDuration other);
}

Now for an implementation of IDuration.

public class DurationImpl implements IDuration {

int hours;
int minutes;
int seconds;

public DurationImpl(int hours, int minutes, int seconds) {
    if (hours < 0  || minutes < 0 || seconds < 0) {
        throw new IllegalArgumentException("Durations cannot be negative");
    }
    
    this.hours = hours;
    this.minutes = minutes;
    this.seconds = seconds;
}

public DurationImpl(long seconds) {
    if (totalSeconds < 0) {
    }
    
    int seconds = (int) (totalSecondsOther % 60);
    int minutes = (int) (totalSecondsOther/60) % 60;
    
    if ( totlaSeconds/3600 > Integer.MAX_VALUE) {
        throw new IllegalStateException("Overflow in plus.")
    }
    
    int hours = (int) (totalSecondsOther/36000);
    
    this.hours = hours;
    this.minutes = minutes;
    this.seconds = seconds;
}

@Override
public int compareTo(IDuration o) {
    return Long.compare(this.inSeconds(), o.inSeconds());
}

@Override
public boolean equals(Object obj) {
    if (this =  obj) {
        return true;
    }
    
    if (!(obj instanceof IDuration) ) {
        return false;
    }
    
    IDuration other = (IDuration) obj;
    
    return this.inSeconds() == other.inSeconds();
}

@Override
public String toString() {
    return this.toHms();
}

@Override
public String asHms() {
    // H:MM:SS
    return Strng.format("%02d:%02d:%02d", this.hours, this.minutes, this.seconds);
}

@Override
public IDuration plus(IDuration other) { //ex. 3661 = 1 hour, 1 minute, 1 second
    return new DurationImple(this.inSeconds() + other.inSeconds());
}

}

@Override
public long inSeconds() {
    // It would be best to make sure that this doesn't throw an error
    return this.hours * 3600 + this.minutes*60 + this.seconds;
}

Lets take a look at something that can break the code. Here are the fields:

int hours;
int minutes;
int seconds;

Now for tests:

public class DurationImplTest() {
    // Never do this!! This type should be IDuration
    DurationImpl duration1;
    
    // This will be run before any other tests are run. 
    // For initializing your data
    @Before
    public void setUp() throws Exception {
        duration1 = new DurationImpl(0, 0, 60);
    }
    
    @Test
    public void inSeconds() {
        duration1.seconds = -60;
        // This isn't right. We want to enforce this.
        // To do so, we have to use access modifiers.
    }
}

Use:

private final int hours;
private final int minutes;
private final int seconds;

Prevents mutability without our permission. Only the class has access to their fields. Now, what about final? Once you initialize it, you cannot change it. It acts like a constant. Always start with final and private. Remove final if necessary.

Correct constructor:

private int addCheckExceptoin(int value1, int value2, String message) throws IllegalStateException {
    int result = value1 + value2;
    if (result < 0) throw new IllegalArgumentException(message);
    return result;
}

public DurationImpl(int hours, int minutes, int seconds) {
    if (hours < 0  || minutes < 0 || seconds < 0) {
        throw new IllegalArgumentException("Durations cannot be negative");
    }
    
    // Adding the seconds overflow to the minutes, making 
    // sure that seconds is between 0 and 59
    if (seconds > 59) {
        minutes = addCheckExecpetion(minutes + seconds/60, "Minutes overflow");
        seconds = seconds % 60;
    }
    
    // Adding the minutes overflow to the hours, making 
    // sure that minutes is between 0 and 59.
    if (minutes > 59) {
        hours = addCheckExpection(hours + minutes/60, "Hours overflow");
        minutes = minutes %60;
    }
    
    this.hours = hours;
    this.minutes = minutes;
    this.seconds = seconds;
}

Here is another implementation (finish for homework):

public CompactDurationImpl(long totalSeconds) {

    if (totalSeconds < 0) {
        throw new IllegalArgumentException("Seconds cannot be negative");
    }
    
    this.totalSeconds = totalSeconds;

}

Also make an AbstractDuration that implements IDuration.

Testing Exceptions

@Test(Expected = IllegalArgumentException.class)
public void testNegativeDuration() {
    IDuration tempDuration = new DurationImp(-1);
}

Tests with Messages

@Test
public void testNegativeDurationWithMessage() {
    try {
        IDuration tempDuration = new DurationImg(-1);
        
    } catch(IllegalArgumentException e) {
        assertEquals("Duration cannot be negative", e.getMessage());
    }
}

Debugging

Put break point where you want the program to stop. It then opens a panel of information with a stacktrace which tells you the methods that have been called

Step over: executes and jumps the line

Step into: Steps into the line and breaks at the first line called.

Extending The Design (Duration Example)

Say that you only want to use one field.

You don’t want to go back and change your entire implementation (this will also ruin tests).

We will be learning how to test both of these implementations at the same time (abstract test class).

When you find yourself copying and pasting, you should be using an abstraction.

Abstract Classes:

Think about the helper method addCheckException. We want to put it in the abstract class.

Never add public methods to the classes that aren’t in the interface! Also, be very careful adding new methods to the interface.

/**
 * Abstract base class for implementations of {@link Duration}.
 */
abstract class AbstractDuration implements Duration {
  /**
   * Constructs a {@link Duration} in a manner selected by concrete
   * subclasses of this class.
   *
   * @param inSeconds the length in seconds
   * @return the new {@code Duration}
   */
  protected abstract Duration fromSeconds(long inSeconds);

  @Override
  public String toString() {
    return asHms();
  }

  @Override
  public boolean equals(Object that) {
    if (this == that) {
      return true;
    }

    if (! (that instanceof Duration)) {
      return false;
    }

    return ((Duration) that).inSeconds() == this.inSeconds();
  }

  @Override
  public int hashCode() {
    return Long.hashCode(inSeconds());
  }

  @Override
  public int compareTo(Duration that) {
    return Long.compare(this.inSeconds(), that.inSeconds());
  }

  @Override
  public Duration plus(Duration that) {
    return fromSeconds(this.inSeconds() + that.inSeconds());
  }

  /**
   * Converts an unpacked hours-minutes-seconds duration to its length
   * in seconds.
   *
   * @param hours the number of hours
   * @param minutes the number of minutes
   * @param seconds the number of seconds
   * @return the duration in seconds
   */
  protected static long inSeconds(int hours, int minutes, int seconds) {
    return 3600 * hours + 60 * minutes + seconds;
  }

  /**
   * Formats an unpacked hours-minutes-seconds duration in the same
   * {@code H:MM:SS} format that {@link Duration#asHms()} returns.
   * Assumes that
   *
   * @param hours the number of hours
   * @param minutes the number of minutes
   * @param seconds the number of seconds
   * @return formatted duration
   * @throws IllegalArgumentException if any argument is negative
   */
  protected static String asHms(int hours, int minutes, int seconds) {
    return String.format("%d:%02d:%02d", hours, minutes, seconds);
  }

  /**
   * Ensures that the hours, minutes, and seconds are all non-negative.
   * Is factoring this out overkill? Or should we also factor out the
   * {@code inSeconds < 0} check in the two unary constructors? Discuss.
   *
   * @param hours the number of hours
   * @param minutes the number of minutes
   * @param seconds the number of seconds
   * @throws IllegalArgumentException if any argument is negative
   */
  protected static void ensureHms(int hours, int minutes, int seconds) {
    if (hours < 0 || minutes < 0 || seconds < 0) {
      throw new IllegalArgumentException("must be non-negative");
    }
  }

  /**
   * Returns the number of whole hours in the given number of seconds.
   *
   * @param inSeconds the total number of seconds
   * @return the number of hours
   * @throws ArithmeticException if the correct result cannot fit in an
   *          {@code int}.
   */
  protected static int hoursOf(long inSeconds) {
    if (inSeconds / 3600 > Integer.MAX_VALUE) {
      throw new ArithmeticException("result cannot fit in type");
    }

    return (int) (inSeconds / 3600);
  }

  /**
   * Returns the number of whole minutes in the given number of seconds, less
   * the number of whole hours.
   *
   * @param inSeconds the total number of seconds
   * @return the number of remaining minutes
   */
  protected static int minutesOf(long inSeconds) {
    return (int) (inSeconds / 60 % 60);
  }

  /**
   * Returns the number of seconds remaining after all full minutes are
   * removed from the given number of seconds.
   *
   * @param inSeconds the total number of seconds
   * @return the number of remaining seconds
   */
  protected static int secondsOf(long inSeconds) {
    return (int) (inSeconds % 60);
  }
}

Factory Method - Design Pattern

Essentially, you are abstracting over the constructor.

You know you need to construct an object, but you don’t know which one in the abstract class.

// In Abstract class
public IDuration plus(IDuration other) {
    return makeDurationFromSeconds(this.inSeconds() + other.inSeconds());

protected abstract IDuration makeDurationFromSeconds(long seconds);
// in DurationImpl class
protected IDuration makeDurationFromSeconds(long seconds) {
    return new DurationImpl(seconds);
}

You can get the super class’s implementation of a method by doing:

this.hours = super.getHours(totalSeconds);

Static Fields and Static Methods

static means that a certain thing (field, method, etc) is shared across instances.

public interface IDatabase {
   // int counter = 0; // Already public static final
   void addUser(String username);
}

public class Database implements IDatabase {
    // Remember, depend on the interface
    private final List<String> users;
    private static int counterForConstructor = 0;
    
    public DataBase() {
        this.users
        // you can also do Database.counterForConstructor++;
        counterForConstructor++;
    }
    
    @Override
    public static int getTotalDatabases() {
        return counterForConstructor;
    }
    
    @Override
    public void addUser(String username) {
        // Depending on the requirements, you can also use
        // an IllegalArgumentException
        Objects.requireNonNull(username);
        
        this.users.add(username);
    }
}

// Test
public class DatabaseTest {
    IDatabase database1;
    IDatabase database2;
    IDatabase database3;
    
    @Before
    public void init() {
        // How can I count how many times have I called the constructor?
        database1 = new Database();
        database2 = new Database();
        database3 = new Database();
    }
    
    @Test
    public void testNumberOfDBCreated() {
        int totalDatabases = Database.getTotalDatabases();
        
        assertEquals(3, totalDatabases);
    
    }
    
    @Test
    public void addUser() {
    
    }
}

Singleton Pattern

What if you only want one Database? How can we make the constructor return the same object every time it is called?

// Uncomment if you only want one instance
// private static IDatabase database = new Database();
// You can also put a limit to how many databases:
private static int numDatabases = 0;
private static int maxDatabases = 2;

private Database() {
    this.users = new ArrayList<>();
    
}

public static IDatabase getDatabase() {
    if (numDatabases < maxDatabases) {
        numDatabases++;
        return new Database();
    }
    throw new IllegalStateException("You reached the maximum number of databases");
    // return database; // if you never want two different instances
    // It always returns the same object
    
}
// In Tester class

database1 = Database.getDatabase();
database2 = Database.getDatabase();
database3 = Database.getDatabase();

public void addUser() {
    database1.addUser("User 1");
    database2.addUser("User 2");
    database3.addUser("User 3");
    
    assertEquals(3, dataabse1.getNumUsers()); // Passes
}

Observers:

public interface IDatabase {
    // see above for the rest
    // I want an observer so I can see all the usernames in the databases
    void addUser(IUser username);
    List<IUser> getAllUsers();
    boolean containsUsername(String username);
}

public class Databases implements IDatabase {
    public List<String> getAllUsers() {
        return new ArrayList<String>(this.users); // shallow copy.
    }
    
    public boolean containsUsername(String username) {
        for (IUser user : this.users) {
            if (user.getUsername().equals(username)) {
                return true;
            }
        }
        return false;
    }
}

interface IUser {
    String getUsername();
    void setUsername(String username);
}

public class User implements IUser {
    private String username;
    
    public User(String username) {
        Objects.requireNonNull(username);
        this.username = username;
    }
    
    @Override
    public String getUsername() {
        return this.username;
    }
    
    @Override
    public void setUsername(String username) {
        this.username = username;
    }
}


// Tests
database1.addUser(new User("User1");
database2.addUser(new User("User1");
database3.addUser(new User("User1");

// This is an example of Shallow Copying
List<IUser> users = database1.getAllUsers();
users.clear();
users.get(2).setUsername("User4");

assertEquals(true, database1.containsUsername("User 2"));

Bugs with References

Shallow copying of ArrayList:

List<IUser> shallowCopy = new ArrayList<>();
for (IUser user : this.users) {
    shallowCopy.add(user);
}

Deep copying of ArrayList:

List<IUser> shallowCopy = new ArrayList<>();
for (IUser user : this.users) {
    shallowCopy.add(new User(user.getUsername()));
}
private Database(List<IUser> users) {
    this.users = users; // Bad idea -- shallow copy!
    // instead, do the following:
    this.users = new ArrayList<>(users); // only do this if the 
    // User class is immutable 
    // If it is mutable, do this:
    List<IUser> copy = new ArrayList<>();
    for (IUser user : users) {
        copy.add(user.clone());
    }
    this.users = copy;
}

For this differentiation, you can have a separate interface:

public interface IMutableUser extends IUser {
    void setUsername(String username);
    // And, of course, remove this method from `IUser`
}

You can also create a clone method in User:

// Also, remember to put this in the interface
public IUser clone() {
    return new User(this.username);
}
// You can also do something like this
public IUser clone(IUser other) {
    User clone = new User(this.username);
    clone.internalData = this.internalData; // Shallow copy
    return clone;
}

The C/C++ way would be to create a copy constructor:

public User(User other) {
    this.username = other.username; // you have to copy all of the fields
    // You also have to be careful with this method because how are you
    // going to copy over complex data? Same reference? Or nested clone?
}

Model, View, and Controller

A common OO technique for structuring graphical programs:

The MVC system allows for a decoupled application. Every class that you write, should only be in one (either Controller, View, or Model). This means that you can remove the Model(/controller/view) and it shouldn’t influence the rest.

Example Game - Tic-Tac-Toe

Model:

Controller:

More analysis: error conditions:

void moveAsX(int column, int row);
void moveAsY(int column, int row);

In this implementation, the client needs to keep track of who’s turn it is. Always give the user the least amount of privileges possible to get the job done. How can we reduce the freedom?

void move(int column, int row);

This way, the user does not need to know who’s turn it is.

boolean isXsTurn();
boolean isYsTurn();

Can we have one method for this?

We need a type that limits our choices and also has meaning. We want a class that cannot be modified, has limited choices, and easily differentiable.

Enumerations

public class PlayerType {
    public final static String X = "X";
    public final static String Y = "Y";
}

// then we can call it with:
PlayerType.X

This is a lot better, but it’s still a String.

This is what enums do behind the scenes:

final public class PlayerType { // final means that it cannot be extended
    private PlayerType() {};
    // Remember objects are references or address
    public final static PlayerType X = new PlayerType();
    public final static PlayerType Y = new PlayerType();
}

Now we can’t break with with other Strings. You have limited amount of choices. We also don’t have to override .equals() because we want to compare addresses.

// This is the same thing as above:
public enum PlayerTypeEnum {
    X, Y;
}

You can also add things to the enum:

public enum PlayerTypeEnum {
    X("X"), Y("Y");
    
    private final String type;
    private PlayerTypeEnum(String type){ 
        this.type = Objects.requireNonNull(type);
    }
    
    @Override
    public String toString() {
        return this.type;
    }
}

For homework 2, the implementation would look like:

public enum Suite {
    DIAMOND("♦"), HEART("♥"), SPADE("♠"), CLUB("♣");
    private final String suite;
    Suite(String suite) {
        this.suite = suite;
    }
    
    @Override
    public String toString() {
        return this.suite;
    }
}

public enum Value {
    private final int value;
    
    ACE(1), TWO(2), THREE(3), FOUR(4), FIVE(5), SIX(6) ..., JACK(11), QUEEN(12), KING(13);
    // private final String stringValue;
    // JACK("J"), QUEEN("Q") ... etc
    
    // You can also overload constructors
    Value(String stringValue) {
    // TODO: Finish
    }
    
    Value(int value) {
        if (value < 1 ||  value > 13) {
            throw new IllegalArgumentException("Card values can be between 1- 13");
        }
        
        this.value = value;
    }
    
    int getValue() {
        return this.value;
    }
}

public class Card implemtnts ICard {
    private final Value value;
    private final Suite suite;
    
    public Card(Value value, Suite suite) {
        this.value = Objects.requireNonNull(value);
        this.suite = Objects.requireNonNull(suite);
    }
    
    @Override
    public Suite getSuite() {
        return this.suite; // Can be a shallow copy because they are final
    }
    
    @Override
    public Value getValue() {
        return this.value;
    }
    
    @Override
    public String toString() {
        return super.toString();
    }
}

The default for a template object is Object.

For homework 2, ICard should be an interface. You should create an interface for most classes that you write.

Wildcard

By putting a question mark ?, you don’t have to declare the parameter in the class/interface.

We want to do the following. That is, we want to pass in a field without declaring the types.

FreecellView view = new FreecellTextView(model);
public class FreecellTextView implements FreecellView  {
// you could also do:
// public class FreecellTextView<ICard> implements FreecellView<ICard> {
    // private final FreecellMode<ICard> model; // Don't do this. This is too tightly coupled
    private final FreecellMode<?> model;
    // You could also do:
    // private final FreecellMode<? extends/implements AnotherObject> model;
    
    public FreecellTextView(FreecellMode<?> model) {
        if (model == null) {
            throw new IllegalArgumentException("Model cannot be null");
        }
        
        this.model = model;
    }
    
    @Override
    public String toString() {
        // We need to use the model observers to generate the 
        // desired output from the homework description
        return "";
    }
}

Builder Pattern

Recall out TicTacToe interface from last lecture:

public interface ITicTacToe {
    PlayerType nextPlaer();
    void move(int x, int y);
    boolean isGameOver();
    PlayerType getWinner();
}

There are many different ways to implement the game.

The board:

public class TicTacToeImpl implements ITicTacToe {
    private final PlayerType [][] board;
    // Or you could do something like:
    // private final ArrayList<ArrayList<PlayerType>> listBoard;
    // Because you already know the size, use the Array implementation -- it saves space
    private final PlayerType [] players;
    private final int winGoal;
    public TicTacToeImple() {
        this.board = new PlayerType[3][3];
        this.players = new PlayerType[2];
        this.players[0] = PlayerType.X;
        this.players[1] = PlayerType.Y;
        this.winGoal = 3;
    }
    @Override
    public PlayerType nextPlayer() {
    }
    
    @Override
    public void move(int x, int y) {
    } 
}

This relates to Homework 2.

What if we want our constructor to be more customizable?

public interface INConnect {
    void move(int col);
    PlayerType getWinner();
    boolean isGameOver();
}

public class NConnectImpl implements INConnect {

    public NConnectImpl() {
        this.width = 4;
        this.height = 4;
        this.winGoal = 3;
        this.players = PlayerType.values();
        
    }
    
    @Override
    public void move(int col) {
    }
    
    @Override
    public PlayerType getWinner() {
    }
    
    @Override
    public boolean isGameOver() {
        return false;
    }
}

We want to keep our fields final, but also we want to have a setter to our user can customize the fields. We also don’t want setters because that means that we can modify the game mid-game. Instead, we use a Builder Pattern.

// Inner class
public class NConnectImpl implements IConnect {
    private NConnectImpl(int width, int height, int winGoal, PlayerType[] players, int depth) {
        // Fill this in
        this.width = width;
        this.height = height;
        this.winGoal = winGoal;
        this.players = players;
        this.depth = depth;
    }
    // Inner class of the NConnectImpl
    public static final class Builder {
        private int width;
        private int height;
        private int winGoal;
        private PlayerType[] players;
        private int depth;
        
        Builder() {
            this.width = 4;
            this.height = 4;
            this.winGoal = 3;
            this.players = PlayerType.values();
            this.depth = 1;
        }
        
        void setWidth(int width) {
            this.width = width;
        }
        
        void setHeight(int height) {
            this.height = height;
        }
        
        void setWinGoal(int winGoal) {
            this.winGoal = winGoal;
        }
        
        void setPlayers(PlayerType[] players) {
            this.players = players;
        }
        
        void setDepth(int dpeth) {
            this.depth = depth;
        }
        
        INconnect build() {
            return new NConnectImpl(this.width, this.height, this.winGoal, this.players, this.dpeth);
        }
    }
}

// When building:
NConnectImpl.Builder builder = NConnectImpl.Builder();
connect = builder.build();

You can also have each of the Builder’s setters return a Builder (this). This means that you could chain them:

connect = (new NConnectImpl.Builder()).setHeight(3).setDepth(2).build();

You can also have a static method in NConnectImpl:

public static Builder getBuilder() {
    return new Builder();
}

This means that you can do this:

connect = NConnectImpl.getBuilder().setHeight(3).getDepth(2).build();

Because no one is going to be using the real constructor, you can modify it all you want.

We want to catch errors in the constructor and not the builder because the builder could be used by multiple class, each with different constraints

For homework 2:

If you have a bunch of chained if-else statements, you should use dynamic dispatch.

public class SimpleFreecellModel implements FreecellModel<ICard> {
    private final List<ArrayList<ICard>> cascadePile;    
    
    // constructor
    cascadePile = new ArrayList<ArrayList<ICard>>();
    
    public void startGame(...) {
        cascadePile.add(deck.get(0));
        cascadePile.add(deck.get(6));
    }
    
    public void move(...) {
        // don't put the entire logic in this method
        if (source == PileTyle.CASCADE && destination == PileType.CASCADE) {
            // Use helper methods here!!
        } else if (source == PileType.CASCADE && destination == PileType.FOUNDATION) {
            // Do the move logic here
        }
        // etc
        
        // instead, do:
        // getPile is a helper method
        IPile sourcePile = getPile(source);
        IPile destPile = getPile(destination);
        
        sourcePile.addCard(destPile.getCard(index));
        destPile.removeLastCard();
    }
}

public interface IPile<T> {
    void addCard(T card);
    void removeLastCard();
    T getLastCard();
    boolean isFull();
}

public class OpenPile implements IPile<ICard> {
    public void addCard(ICard card) {
        // do the move logic here
    }
    // etc.
}

public class CascadePile implements IPile<ICard> {
    // etc.
}

public class CascadePile implements IPile<ICard> {
    // etc.
}

Controller

We normally drive the interaction of our code using our tests.

What if the users of our code aren’t other developers?

public class Main { // This name can be whatever
    // This signature is important, however.
    // The Array of arguments are the command line arguments
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // For the homeworks, we have to make sure that the next is an int
        int num1 = scanner.nextInt();
        int num2 = scanner.nextInt();
        System.out.println(num1 + num2);
        
    }
}

How do we test this?

public class CalculatorImpl implements ICalulator {
    
    @Override
    public int add(int num1, int num2) {
        return num1 + num2;
    }
}

public interface IController {
    void run(ICalculator calculator);
}

public class ControllerImpl implements IController {
    private final Readable in;
    private final Appendable out;
    
    public ControllerImpl(Readable in, Appendable out) {
        this.in = Objects.requireNonNull(in);
        this.out = Objects.requireNonNull(out);
    }
    
    @Override
    public void run(ICalculator calculator) {
        Scanner scanner = new Scanner(this.in);
        int num1 = scanner.nextInt();
        int num2 = scanner.nextInt();
        String result = Integer.toString(calculator.add(num1, num2));
        
        try { // You MUST handle the exception
            this.out.append(result);
        } catch (IOException e) {
            // TODO: figure out what to do
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Readable readable = new InputStreamReader(System.in);
        Appendable outputStream = new PrintStream(System.out);
        
        ICalculator calculator = new CalculatorImpl();
        IController controller = new ControllerImpl(readable, outputStream); 
        controller.run(calculator);
    }
}

In Tester File:

public class CalcImplTest {
    private ICalculator calculator;
    private IController controller;
    
    @Before
    public void setUp() {
        calculator = new CalculatorImpl();
        controller = new ControllerImpl();
    }
    
    @Test
    public void add() {
        assertEquals(7, calculator.add(3, 4));
    }
    
    @Test
    public void testController() {
        Readable stringReader = new StringReader("2 4\n");
        Appendable stringBuilder = new StringBuilder();
        
        ICalculator calculator = new CalculatorImpl();
        
        IController controller = new ControllerImpl(stringReader, outputSteam);
        controller.run(calculator);
        
        assertEquals("7", stringBuilder.toString());
    }
}

Readable and Appendable are higher level than BtyeArrays. We always want to use the highest level possible for out implementation.

Checked and Unchecked Exceptions

Checked exceptions: forced to catch them

Unchecked exceptions: not forced to catch them

Mocks

Consider this bug:

int num1 = scanner.nextInt() + 1;
int num2 = scanner.nextInt();

How do we know if we are reading the input wrong or if the logic is wrong?

To test this, we need to implement a mock calculator

public class MockCalculator implements ICalculator {
    private final Appendable log;
    
    public MockCalculator(Appendable log) {
        this.log = Objects.requireNonNull(log);
    }
    
    @Override
    public int add(int numm1, int num2) {
        try {
            log.append(num1 + " " + num2);
        } catch (IOException e) {
            // TODO: figure out what to do if this fails
        }
        return 0;
    }
}

// in test class:
@Test
public void testReadingInput() {
    Readable stringReader = new StringReader("3 4\n");
    Appendable mockAppendable = new StringBuilder();
    
    Appendable stringBuilder = new StringReader();
    
    ICalculator mockCalculator = new MockCalculator(mockAppendable);
    
    IController controller = new ControllerImpl(stringReader, stringBuilder);
    controller.run(mockCalculator);
    
    assertEquals("3 4", mockAppendable.toString());
}

Exam 1

All of the Lectures from next week (week of 5/24) will be on the exam


Let’s now add complexity to our add method:

// example input:
new StringReader("+ 3 4 - 5 2 * 3 4 q");

// in Controller class
private void write(String message) {
    try {
        out.append(message);
    } catch (IOException e) {
        System.err.println("Couldn't write to appendable");
    }
}
// in run method
while (scanner.hasNext() ) {
    String input = scanner.next().toLowerCase();
    switch(input) {
        case "q":
            this.write("The game has ended");
            break;
        case "+":
            // TODO: abstract this into a helper method
            if (scanner.hasNext()) {
                // TODO: handle the case that the input is "+q"
                input = scanner.next(); 
                int num1 = 0;
                // TODO: figure out how to know we actually read something into num1 and num2.
                try {
                    num1 = Integer.parseInt(input);
                } catch(InputMismatchException e) {
                   // TODO: figure out what we want to do if we don't encounter a number
                   // should we skip it (go to the next token) or throw an exception?
                }
            }
            // We compute the result only if num1 and num2 were read correctly
            // TODO: figure out how to do that
            int result = calculator.add(num1, num2);
            // in this example, the scope of num1 and num2 are incorrect, but you get the idea
            write(Integer.toString(result));
            break;
        case "-":
            // Do this instead
            this.handleMinusOperation(scanner, calculator);
            break;
        case "*":
            break;
        case "/":
            break;
        default:
            // either skip it and go to the next thing or throw an exception
            throw new IllegalStateException("Unknown operation. Quitting");
    }
}

protected void handleMinusOperation(Scanner scanner, ICalculator calculatr) {
    // TODO: do
}

What if we want to handle more than two

public class ControllerImpl2 extends ControllerImpl {
    public ControllerImpl2(Readable in, Appendable out) {
        super(in, out);
    }

    @Override
    protected void handleAddOperation(Scanner scanner, ICalculator calculator) {
        // the new implementation
    }
}

What if we want to add a new case to the switch statement?

Command Pattern

We want to get rid of the switch statements. Instead, we are going to use function objects.

public interface ICalculatorHandleOperation {
    void handleOperation(Scanner scanner, ICalculator calculator);
}

public abstract AbstractCalculatorHandleOperation implements ICalculatorHandleOperation {
    private final Appendable out;
    
    public write(String message) {
        try {
            this.out.write(message)      
        } catch (...) {
            // figure out what to do
        }
    }
    
    public AbstractCalculatorHandleOperation (Appendable out) {
        this.out = Objects.requireNonNull(out);
    }
}

public class CalculatorHandleAddOperation implements ICalculatorHandleOperation {
    public CalculatorHandleAddOperation(Appendable out) {
        super(out);
    }
    
    @Override
    void handleOperation(Scanner scanner, ICalculator calculator) {
        // do the addition
    }
}

// in the run method (the one with all of the switch statements):
while (scanner.hasNext()) {
    String input = scanner.next().toLowreCase();
    IcalculatroHandleOperation operation = null;
    
    switch (input) {
        case "q":
            write("the program has quit")
            break;
        case "+":
            operation = new CalculatorHandleAddOperation(out);
            break;
        case "-":
            operation = new CalculatorHandleMinusOperation(out);
            break;
        // etc
        default:
            // throw exception 
        if (operation != null) {
            operation.handleOperation(scanner, calculator);
        }
    }
}

You still have to modify the switch statement if you want to add new functionality.

// in ControllerImpl class 
protected final Map<String, ICalculatorHandleOperation> operationMap = new HashMap<String, ICalculatorHandleOperation>();

// in constructor
operation.Map.putIfAbsent("+", new CalculatorHandleAddOperation(out));
operation.Map.putIfAbsent("-", new CalculatorHandleMinusOperation(out));
operation.Map.putIfAbsent("*", new CalculatorHandleMultiplyOperation(out));
operation.Map.putIfAbsent("/", new CalculatorHandleDivideOperation(out));
// in run method

if (input.equals("q")) {
    write("Program has quit");
    return ;
}

ICalculatorHandleOperation operation = operationMap.getOrDefault(input, null);
if (operation != null) {
    operation.handleOperation(scanner, calculator);
}

Now we can expand our implementation and add new functionality:

public class ControllerImplV3 extends ControllerImpl {
    public ControllerImplV3(Readable in, Appendable out) {
        super(in, out);
        this.operationMap.putIfAbsent("pow", new CalculatorHandlePowerOperation(out));
    }
}

You can also do this:

public class CalculatorOperationFactory {
    public static Map<String, ICalculatorHandleOperation> getOperations(Appendable out) {
        Objects.requireNonNull(out);
        Map<String, ICalculatorHandleOperation> operationMap = new HashMap<String, ICalculatorHandleOperation>();

        operation.Map.putIfAbsent("+", new CalculatorHandleAddOperation(out));
        operation.Map.putIfAbsent("-", new CalculatorHandleMinusOperation(out));
        operation.Map.putIfAbsent("*", new CalculatorHandleMultiplyOperation(out));
        operation.Map.putIfAbsent("/", new CalculatorHandleDivideOperation(out));
        
        return operationMap;
    }
}

But this gives the same objects back every time. This might not be ideal (depending on what you want).

public class CalculatorOperationFactory {
    
    // @Override
    // public static class AddOperationSupplier Supplier<ICalculatorHandleOperation> {
    //     private final Appendable out;
    //     public AddOperationSupplier(Appendable out) {
    //         this.out = out;
    //     }
    //     
    //     @Override
    //     public ICalculatorHandleOperations get() {
    //         return new CalculatorHandleAddOperation(out)
    //     }
    // }
    
    public static Map<String, Supplier<ICalculatorHandleOperation>> getOperations(Appendable out) {
        Objects.requireNonNull(out);
        Map<String, Supplier<ICalculatorHandleOperation>> operationMap = new HashMap<String, ICalculatorHandleOperation>();

        operationMap.addIfAbsent("*", new Supplier<ICalulatorHandleOperation>() {
            @Override
            public ICalculatorHandleOperation get() {
                return new CalculatorHandleMutiplyOperation(out);
            }
        });
        
        // or you could do something lile
        operationMap.addIfAbsent("/", () -> {return new CalculatorHandleDivideOperation(out);});
        // etc. 
        
        return operationMap;
    }
}

Exam 1 on Tuesday

Assignment due on Friday


Homework 3 (watch the Thursday lecture again if needed)

Example command: C6 1 F2

What about for bad inputs?

C-34 1 F2 is a valid input, but an invalid move. The controller should not check for validity of the move.

try {
    model.move(...);
catch(IllegalArgumentException e) {
    write("Invalid move, enter a new, valid input");
}

But the controller should catch invalid inputs: Ci 1 F2

For the example of: Ci C2 1 F2

Ci = try again

1 = try again

F2 ok.

More input = no such element exception = Illegal state exception Three types of inputs:

If you are dealing with a String, and you are expecting to see an input when there is no input (ie EOF at the end of a String), you should throw an IllegalStateException.

It either has to be a q or the game has to be done in order for you to stop reading.

Example: Ci 1 F2 4 F4 = F2 4 F4 (valid input) = invalid move (model) = invalid move – try again

Example: C3 t 2 F2 = C3 2 F2

Example: C3 t t F2 2 = C3 2 ? = no such element exception = illegal state exception

Example: C1q should be ignored (not quitting the game)

Format: FILE NUMBER PILE

You know you need three inputs

If it is an invalid move, print something that says that.

Remember that you also have to make a mock model

Why not do:

// In PILE NUMBER PILE format
String getValidPile(Scanner scanner) {
    boolean gotValidPile = false;
    while (scanner.hsaNext() && !gotValidPile) {
        String input = scanner.next();
        // what happens if we saw a q and how do I communicate this to
        // run to quit the game.
        // We need to determine if we got a valid pile, if not, keep reading
    }
    // what happens if we couldn't read a valid pile?
    return "";
}
String[] getInput(Scanner scanner) {
    // Put these in function objects so that you can
    // change how we put in inputs
    String pile1 = getValidPile(scanner);
    String number = getValidNumber(scanner);
    // even though it is a String, it grantees that it is a number
    String pile2 = getValidPile(scanner);
}

// in run
public void run() {
    Scanner scanner = new Scanner(in);
    while(scanner.hasNext() ) {
        String[] input = getInput(Scanner);
        try {
            model.move(put_the_input_here);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Readable in = new InputStreamReader(System.in);
        Appendable out = new StringRead("");
    }
}
public class Controller implements IController {
    private final Appendable out;
    private final Readable in;
    
    public Controller(Appendable out, Readable in) {
        //...
    }
    
    public void run() {
        Scanner scanner = new Scanner(in);
        // Every time before you get next, you need to make sure that there is a next
        while (scanner.hasNext()) {
            String input1 = scanner.next();
            String input2 = scanner.next();
            String input3 = scanner.next();
            
            try {
                out.append(input1 + " " + input2 + " " + input 3);
            } catch (IOExcpetion e) {
                throw new IllegalStateException("Couldn't write to append able");
            }
        }
        if ( game not over) {
            throw new IllegalStateException("Game is not over, but we ran out of inputs");
        }
        
        try {
            out.append("game ended.");
        } catch (IOException e) {
            throw new IllegalStateException("Cannot write to appendable");
        }
    }
}

How do you test the IOException?

public class FakeAppendable implements Appendable {
    public Appendable append(...) {
        throw new IOException();
    }
    /...
}

Encapsulation and Invariants

Recall Connect N.

“If you were eating some funny gummy bears, you might be thinking about 4 or 5 dimensions” - Vido

We could make any program have the following field:

Map<Object, Object> properties

With this, we gain flexibility, but we lose meaning.

These are bad freedoms.

“Every program and every privileged user of the system should operate using the least amount of privilege necessary to complete the job”

We can’t enforce the rules of the fields if they are public/the client has freedom.

Principle of Invariants

They state the things that don’t change.

final class Even {
    public Even(int value) {
        if (value % 2 != 0) {
            throw new IllegalArgumentException("value must be even");
        }
        this.value = value;
    }
    
    public int nextValue() {
        return value += 2;
    }
    
    public void reset() {
        this.value = 0;
    }
    
    public int halve() {
        return value /= 2;
        // Does NOT hold the invariant! It mutates the object
    }
    
    public int half() {
        return value / 2;
        // This holds in the invariant
    }
    
}

A class invariant is a logical statement about the instantaneous state of an object that is ensured by the constructors and preserved by the methods.

Not an invariant (relative):

// INVARIANT: value is small

Not an invariant (not instantaneous):

// INVARIANT: value never decreases

Not an invariant (not true):

// INVARIANT: value is non-negative

Not an invariant (True, but vacuous because Java guarantees it):

// INVARIANT: value is an int
private final int width;
// INVARIANT (1): width is not null (no!)
// INVARIANT (2): width > 0 (yes!)
// INVARIANT (3): width > height (no; we don't enforce it!)
// INVARIANT (4): width never changes (no; not instantanous!)

private final turn;
// INVARIANT (5): turn only increases (no)
// INVARIANT (6): turn > 0 (no; not enforced)
// INVARIANT (7): turn < players (yes)

Only 2 and 7 are invariants.

private List<List<Integer>> columns;
// INVARIANT (1): columns != null
// INVARIANT (2): columns.size() == width (yes)
// INVARIANT (3): columns.get(col) != null if 0 <= col < width (yes)
// INVARIANT (4): every column in columns has size <= height (yes) 
// INVARIANT (5): every Integer in columns is a valid player in [0, players)

// NOT AN INVARIANT: columns
// NOT AN INVARIANT: columns agrees with width (don't know what that means) 
// NOT AN INVARIANT: columns always refers to the same list (not instantaneous)

A class-invariant reasoning principle:

Rule out representations you don’t want (whether invalid or merely inconvenient).

Turtle Graphics

You may use a Utils class which has its own requireNonNull() that throws an IllegalArgumentException.

Controller:

public interface IController {
    void run();
}

public class ControllerImpl implements IController {
    private final ITurtleModel model;
    private final Appendable out;
    private final Readable in;
    
    public ControllerImpl(ITurtleModel model, Appendable out, Readable in) {
        this.model = model;
        this.out = out;
        this.in = in;
    }
    
    private void write(String message) {
        try {
            this.out.append(message);
        } catch (IOExcpetion e) {
            throw new IllegalStateException("Couldn't write to append able");
        }
    }
    
    // move 10
    // turn 90
    // move 10
    // turn 90
    // move 10
    // turn 90
    // move 10
    // turn 90
    // This draws a square
    @Override
    public void run() {
        Scanner scanner = new Scanner(this.in);
        
        while(scanner.hasNext()) {
            String input = scanner.next().toLowerCase();
            
            switch (input) {
                case "q":
                    write("The game has quit\n");
                    return;
                case "move":
                    int distance = scanner.nextInt();
                    this.model.move(distance);
                    write("Moving: " + distance + "\n");
                    break;
                case "turn":
                    int angle = scanner.nextInt();
                    this.mode.turn(angle);
                    write("Turning: " + angle + "\n");
                    break;
                case "save":
                    this.model.save();
                    write("Saving.\n");
                    break;
                case "retrieve":
                    this.model.retrieve();
                    write("Retrieved.\n");
                    break;
                default:
                    write("Invalid command. Try again.");
                    break;
            }
        }
    }
}
// in Move case
if (scanner.hasNext()) {
    String commandInput = scanner.next().toLowerCase();
    
    if (commandInput.equals("q") {
        write("The game has quit.");
        return;
    }
    
    try {
        int distance = Integer.parseInt(commandInput);
        this.model.move(distance);
    }
}

You need to know what broke you out of the loop. To do this, use a boolean.

In each switch case:

boolean inputReadCorrectly = false;
while (scanner.hasNext() && !inputReadCorrectly) {
    String commandInput = scanner.next().toLowerCase();
    
    if (commandInput.equals("q")) {
        write("Quit.");
        return;
    }
    
    try {
        int distance = Integer.parseInt(commandInput);
        this.model.move(distance)
        inputReadCorrectly = true;
    } catch (NumberFormatException e) {
        write("Invalid input, try again");
    }
}
// in Switch
case "move":
    // you don't want to use exceptions to handle move flow
    CommandResult result = handleMove(scanner);
    if (handleMove(scanner) == CommandResult.QUIT) {
        write("quit");
        return ;
     } // etc.
    
// in HandleMove
if (commandPair.getKey() == CommandResult.SUCCESS) {
    model.move(commandPair.getValue());
} else if (commandPair.getKey() == CommandResult.RAN_OUT_OF_INPUT) {
    
}

// new File
protected CommandPair getNextInt(Scanner scanner) {
    while(scanner.hasNext()) {
        String stringValue = scanner.next().toLowerCase();
        
        if (stringValue.equals("q")) {
            return new CommandPair(CommandResult.QUIT, null);
        //
        }
        
        try {
            int value = Integer.parseInt(stringValue);
            return new CommandPair(CommandResult.SUCCESS, value);
        } catch (...) {
            // continue going
        }
    }
}
public enum CommandResult {
    SUCESS, QUIT, RANOUT;
}

publlic class CommandPair implements IPair<CommandResult, Interger> {
    private final CommandResult key;
    private final Integer value;
    
    public CommandPair(CommandResult key, int value) {
        this.key = key;
        this.value = value;
    }
    //have getters
}

#

On the exam, you will be asked to add functionality to given implementations/create implementation of an interface.

Say, in the Turtle example, we want to add a square command.

What if we weren’t able to write a helper method/modify our switch statement?

Suppose the exam asks:

Introduce two new features, one of them that traces our moves (remembers the moves as lines) and a method called draw that returns the lines to the user

DO NOT DO:

DO:

Then, create a new class:

Two ways to copy code:

  1. Inheritance - extends
public class DrawingTurtleModelImpl extends SimpleTurtle implements IDrawingTurtle {
    // you can access _protected_ fields here
    public DrawingTurtleModelImpl() {
        super();
        this.lines = new ArrayList<>();
    }
    
    @Override
public void trace(double distance) {
    Position2D start = this.getPosition();
    this.move(distance);
    Position2D end = this.getPosition();
    
    this.lines.add(new Line(start, end));
}

@Override
public List<Line> draw() {
    return new ArrayList<>(this.lines);
    // Okay if the data inside of the list is immutable

}

What happens if the class is final (which means that you can’t extend it)?

  1. Composition (delegation) – try doing this first
    • In this approach, you cannot use the fields
public class DrawingTurtleModelImpl implements IDrawingTurtle {
    private final IDrawingTurtle delegate = SimpleITurtle();
    private final ITurtleModel delegate;
    
    public DrawingTurtleImpl() {
        this.delegate = new SimpleITurtle();
        this.lines = new ArrayList<>();
    }
    
    public DrawingTurtleModelImpl(ITurtleModel delegate) {
        // This is better because we can use different
        // implementations
        this.delegate = Utils.requireNonNull(delegate);
        this.lines = new ArrayList<>();
    }
    
    @Override
    public void trace(double distance) {
        Position2D start = this.delegate.getPosition();
        ...
    }
}

Now what about the controller?

Make a new controller that just handles the new implementation.

public interface ITurtleCommand {
    CommandResult run(Scanner scanner);
}

public abstract class AbstractTurtleCommand implements ITurtle {
    private final ITurtleModel model;
    private final Appendable out;
    

    public AbstractTurtleCommand(ITurtleModel model, Appendable out) {
        this.model = Utils.requireNonNull(model);
        this.out = out;
    }
    
    protected void write(String message) {
        Utils.requireNonNu1l (message) ;
        try {
            this.out.append(message) ;
        }catch(I0Exception e){
            throw new IlLlegalStateException("Couldn't write");
        }
    }
    
    protected void getNextInt(...) {
        ...
    }

}


public class TurtleMoveCommand implements AbstractTurtleCommand {
    private final ITurtleModel model;
    public TurtleMoveCommand(ITurtleModel mode, Appendable out) {
        super(model, out);
    }
    
    @Override
    public CommandResult run(Scanner scanner) {
        ...
    }
    
    
}

public class ControllerImpl {
    ...
    protected final Map<String, ITurtleCommand> commands;
    
    public void run() {
        commands.putIfAbsent("move", new TurtleMoveCommand(model, out));
        commands.putIfAbsent("turn", new TurtleTurnCommand(model, out));
        // You can do things with the model before you quit
        commands.putIfAbsent("q", new TurtleQuitCommand(model, out));
        ... //etc.
        while (scanner.hasNext()) {
            String input = scanner.next().toLowerCase();
            CommandResult result = null;
            ITurtleCommand command = commands.getOrDefault(input, null);
        }
        
        if (command != null) {
            CommandResult result = command.run(scanner);
            if (result == CommandResult.QUIT) {
                write("Quit.");
                return ;
            } else if (result == CommandResult.RAN_OUT_OF_INPUT) {
                throw new IllegalStateException("Ran out of input.");
            }
        }
        
    }
}

public class ControllerImpl2 extends ControllerImpl implements IController {

    private final IDrawingTurtleModel model;
    private final Appendable out;
    private final Readable in;
    
    public ControlLlerImpl3(IDrawingTurtleModel model, Appendable out, Readable in) {
        super(model, out, in);

        // You can't ITurtleCommand because that doesn't take in a DrawingTurtle
        this.commands.putIfAbsent("triangle", new ITurtleCommand() {
            @Override
            public CommandResult run(Scanner scanner) {
                model.move(10);
                model.turn(30);
                return CommandResult.SUCCESS;
            }
        });
    }


    public void run() {
        CommandPair commandPair = getNextInt(scanner); // you should make this static so we are not bound by the abstract class
        
        
    }
}

Exam 1 Review

Exam is open 9:00 am - 6:00 pm

Practice Exam: Question 1

Question 2:

Question 3:

Question 4:

Question 5:

Question 6:

Question 7:

Question 8:

Question 9:

public interface CommandTurtleOperations extends TurtleOperations {
    // javadoc as well
    void runCommand(String command) throw IllegalArgument Exception;
}

Question 10:

public class ParseableTurtleManager implements CommandTurtleOperation {
    private final TurtleOperations delegate;
    
    public ParseableTurtleManager(TurtleOperations delegate) {
        if (delegate == null) {
            throw new IllegalArgumentException("Delegate is null");
        }
        this.delegaet = delegate;
    }
    
    @Override
    public void move(double distance) {
        this.delgate.move(distance);
    }
    
    @Override
    public void runCommand(String command) throw IllegalArgumentException {
        if (Command == null) {
            throw new IllegalArgumentException("Command is null");
        }
        
        String[] tokens = command.split(" ");
        
        if (tokens.length == 0) {
            throw new IllegalArgumentException("No command provided");
        }
        
        if (tokens.length > 2) {
            throw new IllegalArgumentException("Command is too long");
        }
        
        switch (tokens[0]) {
            case "move":
                break;
            case "turn":
                break;
            case "trace":
                break;
            case "save":
                break;
            case "retrieve":
                this.delegate.retrieve();
                break;
            default:
                break;
        }
    }
}

Question 11:

Question 12:

pub
public class TurtleManagerConmanderNew extends TurtleManagerConnander{

}

public List<String> getKnownConmmands() {
    List<String> knownConmands = super.getKnownComnands() ;
    knownConnands.add("moveto");
    return getKnownConmands();
}


public void runConnmand(String command) {
    if (command == null) {
        throw new IllegalArgumentException("Command cannot be null);
    }
    
    // TODO: check for an empty command.
    
    // TODO: extract the command from the string
    // moveto x y => we need to check super.getKnownCommands().contains("trace");
    // move, turn, trace, save, pop
    if (super.getKnownCommands().contains(command)) {
        super.runCommand(command);
    } else if (!getKnownCommands().contains(command)) {
        throw new IllegalArgumentException("Unsupported command");
    } else {
        // currently there is only moveto 23 4
        // we have to parse the command on space (similar to before)
        // x = ...
        // y = ..
        // distance = Math.sqrt(x^2 + y^2);
        // angle = Math.arctang(x/y) make sure y is not 0;
        super.runCommand("turn " + angle);
        super.runCommand("move " + distance);
        super.runCommand("turn " + (angle * -1));
    }
}

Exam Tips

Most important concept in the exam: extending design/introducing new functionality. Take a look at the trace and move.

Inheritance vs. Composition

public class IntSetImpl implements IntSet{

private final Set<Integer> set;

public IntSetImp1(){
this.set = new HashSet<>();
}

@Override
public void add(int value) {
    this.set.add(value);
}

@Override
public void addAll(int... values) {
    for (int value : values) {
        this.add(value);
    }

}

@Override
public void remove(int value) {
    this.set.remove(value);
}

@Override
public boolean member(int value) {
    return this.set.contains(value);
}

Now we want to introduce the ability to see how many times we have added to the set. Do not modify the interface.

public interface ICountedIntSet extends IntSet {
    int getAddCount();
}

We can do it via inheritance:

public class CountedIntSetImpl extends IntSetImpl implements ICountedIntSet {
    public CounterIntSetImpl() {
        super();
    }
    
    @Override
    public int getAddCount() {
        return this.counter;
    }
    
    @Override
    public void add(int value) {
        super.add(value);
        this.counter++;
    }
    
    @Override
    public void addAll(int... value) {
        super.addAll(value);
        // This will actually increment the counter twice
        // (i.e. the counter will be double the expected)
        this.counter += value.length;
    }
}

There aren’t really rules for this extension

But what’s a better way to do this?

// put this in the parent class
private void _add(int value) {
   /*
   the add code
   */
}

public void add(int value) {
    this._add(value);
}

You can also make the method public final void add(int value);. But that wouldn’t help because then you can’t override

We can also use Delegation:

public class InstrumentedIntSetComposition implements InstrumentedIntSet {
    private int addCount = 0;
    private final IntSet delegate;

    /**
    * Constructs a new instrumented integer set.
    */
    public InstrumentedIntSetComposition() {
        this(new IntSet1());
    }
    
    public InstrumentedIntSetComposition(IntSet delegate) {
        this.delegate = Utils.requireNonNull(delegate);
    }



  /**
   * Returns the count of how many times an element has been added to the set.
   *
   * @return the count of added elements
   */
  public int getAddCount() {
    return addCount;
  }

  @Override
  public void add(int value) {
    delegate.add(value);
    ++addCount;
  }

  @Override
  public void addAll(int... values) {
    delegate.addAll(values);
    addCount += values.length;
  }

  @Override
  public void remove(int value) {
    delegate.remove(value);
  }

  @Override
  public boolean member(int value) {
    return delegate.member(value);
  }
}

Pros and cons of delegation and inheritance:

Writing Tests Across Implementations:

public abstract class AbstractTestClass {
    IntSet intSet;
    
    @Before
    public void setUp() {
        this.countedIntSet = makeModel();
    }
    // tests here
    
    public abstract IntSet makeModel();
}

// you can also make this a static class inside of the test class
public class Model1Test extends AbstractTestClass {
    @Override
    public IntSet makeModel() {
        return new IntSetImpl();
    }
}

// you can also make this a static class inside of the test class
public class Model2Tests extends AbstractTestClass {
    @Override
    public IntSet makeModel() {
        return new CountedIntSetImpl();
    }
}

Function Objects

public interface IModel {
    void addShape(String id, IShape shape);
    void removeShape(String id);
    
    IShape translateX(String id, int x);
    IShape translateY(String id, int y);
    IShape rotate(String id, double degrees);
    
    void replaceShape(String id, IShape shape);
    IShape getShape(String id);
}

public static void main(String[] args) {
    IModel model = new ModelV1();
    model.addShape("R1", new Rectangle(0, 0, 10, 10));
    model.addShape("C1", new Rectangle(0, 0, 10, 10));

    model.translateShapeX("R1", 10);
    model.translateShapeY("C1", 5);
    model.rotateShape("R1");
    /////
    
    IModelV2 modelV2 = new ModelImplV2();
    modelV2.addShape("R1", new Rectangle(0, 0, 10, 10));
    modelV2.addShape("C1", new Rectangle(0, 0, 10, 10));
    modelV2.scale("R1", 1.2);
}


public class ModelImplV1 implements IModel {

    @Override
    public IShape getShape(String id) {
        // return the shape from the map
    }
    
    @Override
    public IShape translateX(String id, int x) {
        checkArgs(id);
        IShape shapeToUpdate = shapes.get(id);
        IShape newShape = null;
        
        switch(shapeToUpdate.getType()) {
            case RECTANGLE:
                newShape = new Rectangle(shapeToUpdate.getX() + x, shapeToUpdate.getY());
                break;
            case CIRCLE:
                newShape = new Circle(shapeToUpdate.getX() + x, shapeToUpdate.getY());
                break;
        }
        
    return newShape;
    }
    
    public void replaceShape(String id, IShape shape) {
        if (!this.shapes.containsKey(shape)) {
            throw new IllegalArgumentException(...);
        }
        
        this.shapes.replace(id, shape);
    }
}

public interface IModelV2 extends IModel {
    IShape scale(String id, double factor);
    IShape skew(String id, double angle);
}

public class ModelImplV2 extends ModelImplV1 implements IModelV2 {
    @Override
    public IShape scale(String id, double factor) {
        checkArgs(id);
        IShape shapeToUpdate = shapes.get(id);
        IShape newShape = null;
        
        switch(shapeToUpdate.getType()) {
            case RECTANGLE:
                newShape = new Rectangle(shapeToUpdate.getX()*factor, shapeToUpdate.getY()*factor);
                break;
            case CIRCLE:
                newShape = new Circle(shapeToUpdate.getX() * factor, shapeToUpdate.getY() * factor);
                break;
        }
        
    return newShape;
    }
    
    @Override
    public IShape skew(String id, double angle) {
        checkArgs(id);
        IShape shapeToUpdate = shapes.get(id);
        IShape newShape = null;
        
        switch(shapeToUpdate.getType()) {
            case RECTANGLE:
                newShape = new Rectangle(shapeToUpdate.getX()*factor, shapeToUpdate.getY()*factor);
                break;
            case CIRCLE:
                newShape = new Circle(shapeToUpdate.getX() * factor, shapeToUpdate.getY() * factor);
                break;
        }
        
    return newShape;
    }
}

public interface IShape {
    double getX();
    double getY();
    double getH();
    double getW();
    ShapeType getType();
}

public class Rectangle {
}

Is this a good way to scale things? That is, should we be making a new interface for all of the new functionality? What if we wanted to do composite (i.e. chained) transformations?

// in main class

// Transformations on shapes
// a composite transformation => first translate then rotate
IShape newShape = model.translateX("R1", 10);
model.replaceShape("R1_V2", newShape);
// newShape = model.rotate("R1_V2", 20);
newShape = model.rotate(newShape, 20); // we would have to change the signature for that
// then you can make a convenience method:
newShape = model.rotate(model.getShape("R1"), 10);

What about extending the design without writing new models? All of them take a shape in and a number. Can I provide these to the model instead of them being tightly coupled to the model?

// in main
IOManager filemanager = new FileManager(new File("./inputShape1.txt"));
IOManager keyboardIOManager = new StreamManager(...);
IModelNewDesignV1 modelNewDesignV1 = new ModelNewDesignV1Impl(fileManager, fileManager, keyboardIOManager);
IShape newShape1 = modelNewDesignV1.applyTransformation("R1", new Translate(10));
IShape newShape1 = modelNewDesignV1.applyTransformation("R1", new Rotate(10));

public interface IModel {
    void addShape(String id, IShape shape);
    void removeShape(String id);
    
    IShape applyTransformation(String id, ITransformation transformation);
    
    IShape getShape(String id);
}

public class ModelNewDesignV1Impl implements IModel {
    protected final Map<String, IShape> shapes;
    
    public ModelNewDesignV1Impl() {
        this.shapes = new HashMap<String, IShape>();
    }
    
    public ModelNewDesignV1Impl(IOManager... managers) { // see below for IOManager
        this();
        for (IOManager manager : managers) {
            this.shapes.put("RAND_ID?", manager.apply());
        }
    }
    
    public static class Builder {
        List<IShape> shapes;
        
        public Builder() {
            shapes = new ArrayList<>();
        }
        
        public Builder readShape(File file) {
            IShape newShape = new Rectangle(0, 0, 10, 10);
            return this;
        }
        
        public IModelNewDesignV1 build() {
            IModelNewDesignV1 model = new ModelNewDesignV1Impl();
            for (IShape shape : shapes) {
                model.addShape("GenerateID", shape);
            }
            
            return model;
        }
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    @Override
    public IShape applyTransformation(String id, ITransformation transformation) {
        Objects.requireNonNull(id);
        Objects.requireNonNull(transformation);
        
        if (!this.shapes.containsKey(id)) {
            throw new IllegalArgumentException("Id does not exist");
        }
        
        // apply the given transformation
    }
}

public interface ITransformation {
    IShape apply(IShape other);
}

public class TranslateX implements ITransformation {
    private final double x;
    
    public Translate(double x) {
        this.x = x;
    }
    
    @Override
    public IShape apply(IShape other) {
        // do the translation
    }
}

public class TranslateY implements ITransformation {
    private final double y;
    
    public Translate(double y) {
        this.y = y;
    }
    
    @Override
    public IShape apply(IShape other) {
        // do the translation
    }
}

public class TranslateY implements ITransformation {
    private final double degrees;
    
    public Translate(double degrees) {
        this.degrees = degrees;
    }
    
    @Override
    public IShape apply(IShape other) {
        // do the translation
    }
}

The list of IOManagers in the constructor is more decoupled than the Builder. You can also have composite transformations via objects:

public CompositeTransformation implements ITransformation {
    private final ITransformation first;
    private final ITransformation second;

    public CompositeTransformation(ITransformation first, ITransformation second) {
        this.first = first;
        this.second = second;
    }

    public IShape apply(IShape shape) {
        IShape newShape = this.first.apply(shape);
        return this.second.apply(newShape);
    }
}

You could also use a varargs to have chained composite transformations. For the homework, you can also make function objects for different export/import formats. Example:

public interface IOManager {
    IShape apply();
}

public class FileManager implements IOManager {
    private final File file;
    
    public FileManager(File file) {
        this.file = file;
    }
    
    @Override
    public IShape apply() {
        IShape shape = new Rectangle(0, 0, 10, 10) // read these from the file
        return shape;
    }
}

public class StreamManager implements IOManager {
    private final InputStream in;
    // constructor
    
    @Override
    public IShape apply() {
        ...
    }
}

Also, use global variables (i.e. interface with constants).

Getting rid of the switch statements:

// in TranslateX
public IShape apply(IShape shape) {
    Objects.requireNonNull(shape);
    IShape newShape = shape.clone(shape.getX() + this.x, shape.getY(), shape.getW(), shape.getH());
}

public interface IShape {
    public IShape clone(double x, double y, double w, double h);
    public ShapeType getType() {
        return ShapeType.RECTANGLE;
    }
}

How would the clone work if the ellipse’s constructor was:

public Ellipse(double x, double y, double radius) {
    ...
}

We could use the visitor pattern.

Strategy Pattern

Tic-Tak-Toe Model Example:

public interface ITicTakToeModel {
    int getWidth();
    int getHeight();
    int getGoal();
    Piece getPieceAt(int r, int c);
    void setPieceAt(int r, int c, Piece p);
    Status status;
    
}

public interface IController {
    void run();
    ITicTacToeContorller addPlayer(Player p);
}

public class ControllerImpl {
    private final ITicTakToeModel model;
    private final Appendable output;
    private final List<Piece> players;
    // Constructor
    
    // it returns it so you can chain them
    public ITicTacToe addPlayer(Player p) {
        this.players.add(p);
        return this.players;
    }
    
    protected Point2D getHumanPositon() {
        Scanner scanner = new Scanner(System.in);
        int x = scanner.nextInt();
        int y = scanner.nextInt();
        return new Double(x, y);
    }
    
    protected Point2D getAIPosition(Piece thisPiece) {
        // SuperHardAI, try to win.
        // todo: be more smart
        
        
        //MediumAI, try to play the center, then play the corner. 
        // If that fails, then go to easyAI
        if (this.model.getPieceAt(0, 0) != thisPiece &&
                    this.mode.getPieceAt(1, 1) != thisPiece &&
                    this.model.getPieceAt(2, 2) == Piece.EMPTY) {
            return new Point2D.Double(2, 2);
        } else if (...) { 
            // do more blocking here...
        }
        
        // TODO: don't hardcode
        if (this.model.getPieceAt(1, 1) == Piece.EMPTY) {
            return new Point2D.Double(1, 1);
        } else if (this.mode.getPieceAt(0, 0) == Piece.EMPTY) {
            return new Point2D.Double(0, 0);
        } else if (this.mode.getPieceAt(2, 2) == Piece.EMPTY) {
            return new Point2D.Double(2, 2);
        } else if (this.mode.getPieceAt(2, 0) == Piece.EMPTY) {
            return new Point2D.Double(2, 0);
        } else if (this.mode.getPieceAt(0, 2) == Piece.EMPTY) {
            return new Point2D.Double(0, 2);
        }
        // easy AI
        for (int i = 0; i < this.model.getWidth(); i++) {
            for (int j = 0; j < this.model.getheight(); j++) {
                if (this.model.getPieceAt(i, j) == Piece.EMPTY) {
                    return new Point2D.Double(i, j);
                }
            }
        }
        throw new IllegalStateException("Should not be able to play");
    }
        
    private void write(String message) {
        try {
            this.output.append(message);
        } catch(IOException e) {
            System.err.println("Could not write to appendable");
            // or something else
        }
    }
    
    Point2D getNextPositionForPlayer(PlayerType playerType) {
        switch (playerType) {
            case HUMAN:
                return getHumanPosition();
            case AI:
                return getAIPosition();
            default:
                throw new IllegalArgumentException("Invalid PlayerType");
        }
    }
    
    @OVerride
    public void play() {
        int nextPlayer = 0;
        
        if (this.players < 2) {
            throw new IllegalStateException("Needs at least 2 players");
        }
        
        while(this.model.gameState() == Status.PLAYING) {
            this.printBoard();
            boolean playerSuccess = false;
            while (!playerSuccess) {
                //Point2D player = getHumanPosition();
                Point2D playerPos = getNextPositionForPlayer(player.getPlayerType());
                try {
                    // todo: to have this work, you need to have IPlayer have a piece and a player (Either human  or AI)
                    this.model.getPieceAt((int) player.getX(), (int) player.getY(), players.get(nextPlayer).getPiece());
                    playerSuccess = true;
                    nextPlayer = (nextPlayer + 1) % players.size();
                } catch (Exception e) { // catch the spesific exception 
                    // do somethin here
                }
            }
        }
        
        printBoard();
        
        if (this.model.gameState() == Status.WON) {
            write("Player: " + this.model.getWinner() + " won the game.");
        } else {
            write("TIE");
        }
    }
}

public enum PlayerType {
    HUMAN, AI;
}

// main
int h = 3
int w = 3
int g = 3
model = new ModelImpl.Builder().setheight(h).setWidth(w).getGoal(g).build();
controller = new ControllerImpl(model, new PrintStream(System.out));
controller.addPlayer(new PlayerImpl(Piece.X, PlayerType.HUMAN)).addPlayer(new PlayerImpl(Piece.O, PlayerType.AI));

controller.play();

How can we add a new strategy to getAIPosition? We cannot reorder it the way that it is now. What if we wanted to pick what kind of AI we want to play? Also, what if we want to expand our PlayerTypes?

public enum PlayerType { 
    HUMAN, EASY_AI, HARD_AI;
}


protected Point2D getHardAIPosition(Piece thisPiece) {
    if (some condition) {
        // do a move
    } else {
        // fall down
        getMediumAIPosition(thisPiece);
    }
}

protected Point2D getMediumAIPosition(Piece thisPiece) {
    if (some condition) {
        // do a move
    } else {
        // fall down
        getEasyAIPosition(thisPiece);
    }
}
protected Point2D getEasyAIPosition(Piece thisPiece) {
    if (some condition) {
        // do a move
    } else {
        // fall down
    }
}

protected Point2D getAIPosition(Piece thisPiece) {
}

Now, how can we add a SuperHardAI? We would have to add the switch statement, add a new method, and add a new PlayerType. Instead, use function objects. We know to use a function object because all of the methods have the same signature.

public interface IStrategy {
    Point2D getPosition();
}

public abstract class AbstractStategy implements IStragegy {
    private final Piece piece;
    private final ITicTacToeModel model;
    
    public AbstractStategy(Piece piece, ITicTacToeModel model) {
        this.piece = piece;
        this.model = model;
    }
}

public class EasyStrategy extends AbstractStategy {

    public EasyStrategy(Piece piece, ITicTacToeModel model) {
        super(piece, model);
    }
    
    
    @Override
    public Point2D getPosition(Piece piece) {
        // do the thing
        
        if (it fails) {
            // don't fall back here because that would couple it to the easyAI
            return null;
        }
    }
}
public class MediumStrategy implements IStrategy {
    public MediumStategey(Piece piece, ITicTacToeModel model) {
        super(piece, model);
    }
    
    
    @Override
    public Point2D getPosition(Piece piece) {
        // do the thing
        
        if (fails) {
            return null;
        }
    }
}

Now, in the model:

Point2D getNextPositionForPlayer(PlayerType playerType, Piece piece) {
    switch (playerType) {
        case HUMAN:
            return getHumanPostion();
        case EASY_AI:
            return EasyStategy(model, thisPiece).getPosition();
        // etc
    }
    
}

Now you don’t need PlayerType and instead you can just have getPosition.

public class HuamnStategy extends AbstractStrategy {
    // get human input and move if valid
}

Now, to avoid null being passed through, we need to have composite Strategies:

public class CompositeStategy implements IStrategy {
    private final IStategy first;
    private final IStategy second;

    public CompositeStategy(IStategy first, IStategy second) {
        this.first = first;
        this.second = second
    }

    @Override
    public Point2D getPosition() {
        Point2D pos = first.getPosition();
        if (pos == null) {
            pos = this.second.getPosition();
        }
        return pos;
    }
}

Exam 1

Exam 2:

How to approach:

Question 1:

The test makes sure

Question 2:

protected List<Integer> removeDegenerateTriangles(List<Integer> indices) {
   // Don't delete stuff from a list while iterating over it, because you might get a bug. 
   List<Integer> newIndecies = new ArrayList<>();
   
   for (int i = 0; i < indices.size() / 3; i+=3) {
    Set<Integer> set = new HashSet<>();
    set.add(indices.get(i));
    set.add(indices.get(i+1));
    set.add(indices.get(i+2));
    
    if (set.size() == 3) {
        newIndecies.add(i);
        newIndecies.add(i+1);
        newIndecies.add(i+2);
    }
    // if (!(indices.get(i) == indices.get(i+1)  || indicies.get(i) == indices.get(i+2)  || indicies.get(i+1) == indicies.get(i+2))) {
    //     newIndecies.add(i);
    //     newIndecies.add(i+1);
    //     newIndecies.add(i+2);
    // }
   }
}

protected boolean sameTriangle(List<Integer> list1, List<Integer> list2) {
    // check if the two lists are the same
    return indecies1.containsAll(indecies2);
}
protected List<Integer> optimizeMesh(List<Integer> indices) {
    // We have to make sure that there are not same triangles
    // Write a helper to check if two triangles are the same
    List<Integer> optimizedIndecies = new ArrayList<>();
    for (int i = 0; i < indecies.size() / 3; i++) {
        boolean same = false;
        for (int j = i + 1; j < indeices.size()/3; j++) {
            if (sameTriangle(i, j)) {
                same = true;
                break;
            }
        }
        
        if (!same) {
            optimizeIndecies.add(indices.get(i));
            optimizeIndecies.add(indices.get(i+1));
            optimizeIndecies.add(indices.get(i+2));
        }
    }
    
    return optimizedIndecies;
}

public SimpleTriangleMesh(List<Position2D> vertices,List<Integer> indices) throws IllegalArgumentException {
    if ( vertices == null || indices == null || indices.size() % 3 != 0 ){
        throw new IllegalArgumentException("Null arguments.");
    }
    for ( int index : indices ){
        if ( index < 0 || index >= indices.size()){
            throw new IllegalArgumentException("Invalid index");
        }
    }

    this.indices = removeDegenerateTriangles(indices);
    this.indices = optimizeMesh(this.indices);
    
    this.vertices = new ArrayList<Position2D>();
    for (Position2D posn : vertices) {
        this.vertices.add(new Position2D(posn.getX(), posn.getY()));
    }
}

Question 3:

public List<Position2D> getVertices() {
    List<Position2D> copy = new ArrayList<>();
    for (Position2D posn : this.vertices) {
        copy.add(new Position2D(posn.getX(), posn.getY()));
    }
    return copy;
}

Question 4:

public interface TraversableTriangleModel extends TriangleModel {
    ITriangle getTriangle(int index);
}

public interface ITriangle {
    int getV1();
    int getV2();
    int getV3();
}

public class Triangle implements ITriangle {
    private final int v1, v2, v3;
    
    public Triangle(int v1, int v2, int v3) {
        this.v1 = v1;
        this.v2 = v2;
        this.v3 = v3;
    }
    
    // implement all of the getters
}

public class AbstractTraversableTriangleMOdel implements ITraversableTriangleModel {

    public AbstractTraversabLleTriangLeModel(List<Position2D> vertices,List<Integer> indices) throws IllLegalArgumentException {
        if (vertices == null || indices == null) {
            throw new ILlegalArgumentException("NuLL arguments.");
        }
        for (int index : indices) {
            if (invalid index) {
                throw new ILlegalArgumentException("Invalid index");
            }
        }
        
        this.indecies = new ArrayList<>(indices);
        this.vertices = new ArrayList<>();

        for ( Position2D position2D : vertices ){
            this.vertices.add( new Position2D(position2D.getx(), position2D.getY()));
        }
    }

    @Override
    pulic abstract ITraiangle getTriangle(int index);
    
    @Override
    pubic List<Position2D> getVertices() {
        // implement
        return null;
    }
    
    @Override
    pubic List<Integer> getIndices() {
        // implement
        return null;
    }
}

Adapter Pattern

public class Main {
    public static void main(String[] args) {
    }
}

public interface IntSet2 {
  void unionWith(IntSet2 other);

  void differenceFrom(IntSet2 other);

  boolean isSupersetOf(IntSet2 other);

  List<Integer> asList();
}


/**
 * Consider this legacy code! Maybe this particular implementation became outdated/security risk/too inefficient/etc.
 */
public final class IntSet2Impl implements IntSet2 {
  private final Set<Integer> set = new HashSet<>();

  private IntSet2Impl() { }

  public static IntSet2 empty() {
    return new IntSet2Impl();
  }

  public static IntSet2 singleton(int i) {
    IntSet2Impl result = new IntSet2Impl();
    result.set.add(i);
    return result;
  }

  @Override
  public void unionWith(IntSet2 other) {
    set.addAll(other.asList());
  }

  @Override
  public void differenceFrom(IntSet2 other) {
    set.removeAll(other.asList());
  }

  @Override
  public boolean isSupersetOf(IntSet2 other) {
    for (int z : other.asList()) {
      if (! set.contains(z)) {
        return false;
      }
    }

    return true;
  }

  @Override
  public List<Integer> asList() {
    return new ArrayList<>(set);
  }
}

// We should try to do code reuse. WE are going to reuse another existing .
public class IntSet2ImplNew extends IntSet1Impl implements IntSet2 {
    private final IntSet1 delegate;
    
    // Sometimes you want this to be private so that you need to use the factory methods
    public IntSet2ImplNew() {
        super(new IntSet1Impl();
    }
    
    public static IntSet2 empty() {
        return new IntSet2ImplNew();
    }
    
    public static IntSet2 singleton(int value) {
        IntSet2ImplNew intSetNew = new IntSet2ImplNew();
        intSetNew.add(value);
        return intSetNew;
    }
  
    @Override
    public void unionWith(IntSet2 other) {
        for (int i : other.asList() ) {
            this.add(i);
        }
    }

    @Override
    public void differenceFrom(IntSet2 other) {
        for (int i : other.asList()) {
            this.remove(i);
        }
    }

    @Override
    public boolean isSupersetOf(IntSet2 other) {
        for (int i : other.asList()) {
            if (!this.member) {
                return false;
            }
        }
        return true;
    }

    @Override
    public List<Integer> asList() {
        List<Integer> list = new ArrayList<>();
        for (int i : this) {
            list.add(i);
        }
        return list;
    }
    
}

General Form:

public class NewAdapterClass extends Adaptee implements Target {}

Where we want to convert from Adaptee to Target.

Two-Way Adapters

We want to do this:

IntSet2 adapter = IntSet2ImplNew.singleton(2);
IntSet1 adapter2= IntSet2ImplNew.singleton(2);

If you use a delegate, then you have to implement both interfaces.

// We should try to do code reuse.
public class IntSet2ImplNew extends IntSet1Impl implements IntSet2 {
    private final IntSet1 delegate;
    
    // Sometimes you want this to be private so that you need to use the factory methods
    public IntSet2ImplNew(int value) {
        super();
        this.add(value);
    }
    
    public static IntSet2ImpleNew empty() {
        return new IntSet2ImplNew(2);
    }
    
    public static IntSet2ImplNew singleton(int value) {
        IntSet2ImplNew intSetNew = new IntSet2ImplNew();
        intSetNew.add(value);
        return intSetNew;
    }
  
    @Override
    public void unionWith(IntSet2 other) {
        for (int i : other.asList() ) {
            this.add(i);
        }
    }

    @Override
    public void differenceFrom(IntSet2 other) {
        for (int i : other.asList()) {
            this.remove(i);
        }
    }

    @Override
    public boolean isSupersetOf(IntSet2 other) {
        for (int i : other.asList()) {
            if (!this.member) {
                return false;
            }
        }
        return true;
    }

    @Override
    public List<Integer> asList() {
        List<Integer> list = new ArrayList<>();
        for (int i : this) {
            list.add(i);
        }
        return list;
    }
    
}

When you use a delegate, you call it an object adapter.

When you use inheritance (like the example above), you call it class adapter.

ViewModel

public interface IModel {
    // Mutating methods
    void addShape(String id, Shape shape);
    void removeShape(String id);
    
    List<String> getAllShapeIds();
    Shape getShape();
}

public class Model implements IModel {
    void addShape(String id, Shape shape) {}
    void removeShape(String id) {}
    
    List<String> getAllShapeIds() {return null;}
    Shape getShape() {return null;}
}

// The view is only there to display model information. Should not mutate.
// Least amount of privilege 
public interface IView {
    void renderModel(IModel model);
}

public class View implements IView {
    protected void drawShape(Graphics g, Shape shape) {
        // todo: draw the shape
    }
    
    public void renderModel(IModel model) {
        for (String id : model.getAllShapeIds()) {
            Shape shape = model.getShape(id);
            drawShape(...);
        }
        
        // In C++, you can say a method is const meaning that it does not mutate the object
        // And in methods you can say (const IModel model) meaning that you cannot change it
        // There is no such thing in Java, so you can do this (when you shouldn't be able to):
        model.removeShape(...);
    }
}

Instead, you should use a IViewModel which only holds the methods that do not mutate the model.

public interface IModel extends IViewModel {
// methods that can mutate
}

public interface IViewModel {
// methods that cannot mutate
}

// Now, the renderModel method should look like

public void renderModel(IViewModel model) { ... }

// Now you can do something like
IModel model = new Model();
IView view = new View();
view.renderModel(model); // It will automatically downcast

// Avoid doing this because it breaks the promise of the interface
public class ViewOnlyModelAdapter implements IModel {
    private final IModel delegate;
    
    // implement all getters
    
    public void addShape(String id, Shape shape) {
        throw new UnsupportedOperationException("Unable to mutate object in ViewOnly");
    }
    
    // do the same for all methods that mutate
}

Exam 2:

How to do code reuse:

Decorator Pattern

The decorator pattern is the same as the strategy pattern except it calls super (similar to a linked list). They are nestable.

public interface IDrink {
    double getPrice();
    String getDescription();
}

public class ADrink implements IDrink{
    protected final double price;
    protected final String description;
    
    public ADrink(double price, String description) {
        this.price = price;
        this.description = description;
    }
    
    public double getPrice() {
        return this.price;
    }
    
    public String getDescription() {
        return this.description;
    }
}

public class Coffee extends ADrink {
    public Coffee() {
        super(3.00, "Free grazed range free no antibiotics coffee");
    }
}

public class OtherDrink extends ADrink {
    public OtherDrink() {
        super(4.00, "Another drink");
    }
}

// main
List<IDrink> order = new ArrayList<>();
order.add(new Coffee());
order.add(new OtherDrink());

double totalPrice = 0;
String receipt = "";
for (IDrink drink : order) {
    totalDrink += drinks.getPrice();
    receipt += drinks.getDescription();
}

// other version of main

ICompositeDrink coffee = new Coffee();
coffee.add( new SomeShot() );
coffee.add( new AnotherShot() );
coffee.description();
coffee.totalPrice();


public interface ICompositeDrink {
    void add(IDrink drink);
}

public class Coffee extends ADrink implements ICompositeDrink {
    List<IDrink> additions;
    
    public Coffee() {
        super(2.00, "A description of a coffee");
        additions = new ArrayList<IDrink>();
    }
    
    public void add(IDrink drink) {
        // if not null
        additions.add(drink);
    }
    
    public double totalPrice() {
        double totalPrice = 0;
        for (IDrink drink : additions) {
            totalPrice += drink.getPrice();
        }
        return totalPrice + this.getPrice();
    }
    
    public String getDescription() {
        return this.description();
    }
}

Example - Drinks

public abstract class ADecoratedDrink {
    protected final double price;
    protected final String description;
    
    public ADecoratedDrink(double price, String description) {
        this.price = price;
        this.description = description;
    }
    
    public double getPrice() {
        return this.price;
    }
    
    public String getDescription() {
        return this.description;
    }
}

public class DecoratedCoffee extends ADecoratedDrink {
    protected final IDecoratedDrink delegate;
    
    public DecoratedCoffee() {
        super(3.5 "Coffee");
        delegate = null;
    }
    
    public DecoratedCoffee(IDecoratedDrink delegate) {
        super(3.5 "Coffee");
        this.delegate = delegate;
    }
    
}
// main

IDecoratedDrink drink  = new DecoratedCoffee(new DecoratedExpressoShot(new DecoratedMilk()));

drink.getDescription();
drink.getPrice();

Example - Writers

    Writer out = null;

    try {
      out = new FileWriter("outwriter.txt");
    }
    catch (IOException e) {

    }
    String input = "I love Object Oriented Design. "
                   + "THIS IS A GREAT COURSE!";



    //a regular print writer, writing to file.
    PrintWriter pw = new PrintWriter(out);
    pw.println(input);

    //a printing, lower-casing writer. Note, to the same file!
    pw = new PrintWriter(new LowerCaseWriter(out));
    pw.println(input);

    //a printing, shift-ciphering writer. Note, to the same file!
    pw = new PrintWriter(new ShiftCipherWriter(out));
    pw.println(input);

    //a printing, lower-casing,shift-ciphering writer. Note, to the
    // same file!
    pw = new PrintWriter(new LowerCaseWriter(new ShiftCipherWriter(out)));

    pw.println(input);


    pw.close();

Practice Exam 2

Question 1:

// do this same thing for different input words as well
public void testCases(){
    IPrefixWordFinder finder = new WordFinder();
    finder.add("FINDER");
    finder.add("FINLAND");
    finder.add("AnotherWord");
    finder.add("FISHY");

    List<String> finPrefix = finder.getWordsWithPrefix("fin");
    List<String> FINPrefix = finder.getWordsWithPrefix("FIN");
    List<String> fInPrefix = finder.getWordsWithPrefix("fIn");
    
    assertTrue(finPrefix.size() == 2);
    assertTrue(FINPrefix.size() == 2);
    assertTrue(fInPrefix.size() == 2);

    assertEquals(new ArrayList<String>(Arrays.asList("FINDER", "FINLAND")), finPrefix);
    assertEquals(new ArrayList<String>(Arrays.asList("FINDER", "FINLAND")), FINPrefix);
    assertEquals(new ArrayList<String>(Arrays.asList("FINDER", "FINLAND")), fInPrefix);
}

Question 2:

(a) describe the test:

This test tests to make sure that adding a word multiple times does not add the duplicates.

@Test
public void testMystery() {
    IPrefixWordFinder finder = new WordFinder();
    IPrefixWordFinder finder2 = new WordFinder();
    String sentence = "This is a great good sentence for Thorough testing";
    String[] words = sentence.split("\\s+");
    for (String s : words) {
        finder.add(s);
        finder2.add(s);
    }

    for (String s : finder.getWordsWithPrefix("")) {
        finder2.add(s);
    }

    assertTrue(finder.getWordsWithPrefix("sent").contains("sentence"));
    assertEquals(finder.getWordsWithPrefix("Th").size(),
                    finder2.getWordsWithPrefix("Th").size());
}

(b) fix the bug in the code so that the test passes

// in wordfinder:
@Override
public void add(String s) {
    Objects.requireNonNull(s);

    WordNode current = root;

    for (int i = 0; i < s.length(); i++) {
        if (!current.children.containsKey(s.charAt(i))) {
            current.children.put(s.charAt(i),new WordNode());
        }
        current = current.children.get(s.charAt(i));
    }
    if (!current.words.contains(s))  {
        current.words.add(s);
    }
}

Question 3

Extend the design to filter out words

public class FilteredWordFinder implements IFilteredWordFinder {
  private final IPrefixWordFinder delegate;
  private final List<String> notAllowedWords;


  public FilteredWordFinder() {
    this.delegate = new WordFinder();
    this.notAllowedWords = new ArrayList<>();
  }


  @Override
  public void addBlockedWord(String... words) {
    // make sure to check for nulls
    for (String s : words) {
      if (!this.notAllowedWords.contains(s)) {
        this.notAllowedWords.add(s);
      }
    }
  }

  @Override
  public boolean isBlockedWord(String word) {
    // check for nulls
    return this.notAllowedWords.contains(word);
  }

  @Override
  public void add(String s) {
    // check for nulls
    if (!this.notAllowedWords.contains(s)) {
      this.delegate.add(s);
    }
    // you can setup an else statement if you what that can throw an exception/do something
    // to tell the user that the given word is not allowed
  }

  @Override
  public List<String> getWordsWithPrefix(String prefix) {
    // check for nulls
    // you also should filter the words out of here (they could have come in via the delegate)
    return this.delegate.getWordsWithPrefix(prefix);
  }
}

Question 4:

Instead of prefix, extend the functionality to use suffixes.

public class RhymingDictionary implements IRhymingDictionary {

  private final IPrefixWordFinder inverseDelegate;

  public RhymingDictionary() {
    this.inverseDelegate = new WordFinder();
  }

  // Reverses a String
  private static String reverse(String s) {
    StringBuilder reversed = new StringBuilder();
    for (int i = s.length() - 1; i >= 0; i--) {
      reversed.append(s.charAt(i));
    }
    return reversed.toString();
  }

  @Override
  public void add(String s) {
    this.inverseDelegate.add(reverse(s));
  }

  @Override
  public List<String> getRhymingWords(String word) {
    if (word == null) {
      throw new IllegalArgumentException("Word cannot be null");
    }
    // We can do this because the requirements for something
    // to rhyme (according to the interface) is if they share a substring (of length greater
    // than 1) at the end. The minimum substring is of length one (i.e. just the last character).
    char suffix = word.charAt(word.length() - 1);
    List<String> reversed_output = this.inverseDelegate.getWordsWithPrefix(suffix + "");
    List<String> output = new ArrayList<>();
    for (String s : reversed_output) {
      output.add(reverse(s));
    }
    return output;
  }

}

GUIs


public class MyWindow extends JFrame {
    private final JButton toLowerCase;
    private final JButton toUpperCase;
    private final JTextField inputTextField;
    private final JLabel outputTextLabel;
    
    public MyWindow() {
        super();
        setSize(ew Dimension(600, 600));
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        
        setLayout(new FlowLayout());
        
        toUpperCase = new JButton("To uppercase");
        toLowerCase = new JButten("To lowercase");
        inputTextField = new JTextField(30);
        outputTextLabel = new JLabel("Label!");
        
        // toUppercaseButton.addActionListener(new ToUpperCaseListener(input));
        // you can use anon classes
        toUppercaseButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String text = inputTextField.getText();
                outputTextLabel.setText(text.toUpperCase());
                System.out.println("ToUpperCase was pressed");
            }
        });
        
        // or you can use lambdas
        toUppercaseButton.addActionListener((ActionEvent e) -> {
            outputTextLabel.setText(inputTextField.getText()); 
        });
        
        // or you can do
        // then you also need _this_ to implement ActionListener
        // toUppercaseButton.addActionListener(this);        
        
        // but using _this_, how can we have multiple buttons?
        toUpperCaseButton.setActionCommand("toUpperCase");
        toLowerCaseButton.setActionCommand("toLowerCase");
        
        toUpperCaseButton.addKeyListener(new KeyListenerForUpperCaseButton());
        
        
        add(toUpperCase);
        add(outputTextLabel);
        add(inputTextField);
        add(toLowerCase);
        
        pack(); // Minimum size for the window
    }
    
    public void ActionPerformed(ActionEvent e) {
        // Replace this with a command pattern
        switch (e.getActionCommand()) {
            case "toUpperCase":
                String text = inputTextField.getText();
                outputTextLabel.setText(text.toUpperCase());
                break;
            case "toLowerCase":
                String text = inputTextField.getText();
                outputTextLabel.setText(text.toLowerCase());
                break;
        }
    }
    
    private class MouseListenerForUpperCaseButton implements MouseAction {
        // implement the methods
    }
    
    // Listens 
    private class KeyListenerForUpperCaseButton implements KeyListner {
        // implement the methods
    }
    
}

// in main

MyWindow myWindow =  new MyWindow();
myWindow.setVisibiliy(true);

How do GUIs fit into MVC?

public interface IModel {
    void writeData(String data);
    String getData();
}

public class Model implements IModel {
    private String data;
    
    public model() {
        this.data = "";
    }
    
    public void writeData(String data) {
        this.data = data;
    }
    
    public String getData() {
        return data;
    }
}

public interface IController {
    void run();
}

public class Controller implements IController, ActionListner {
    private final MyWindow view;
    private final IModel model;
    
    public Controller(MyWindow view, IModel model) {
        this.view = view;
        this.model = model;
        // implement this method in view
        this.view.addViewEventListner(this);
    }
    
    public void run() {
        
    }

    // in actionPerformed:

    switch (e.getActionCommand()) {
        case "toUpperCase":
            String text = view.getData();
            
            break;
        case "SaveButton":
            text = inputTextField.getText();
            model.writeData(text);
            break;
}


    // implement the other ones
}

// Main

IModel model = new Model();
MyWindow myWindow = new MyWindow(model);
myWindow.setVisible(true);

IController controller = new Controller(model, view);
controller.run();

Controllers take in a high level event and relay it to the model.

public interface IView {
    void setData(String data);
    String getData();
    void addViewEventListener(ActionListener listener);
}

Subscriber/Receiver Pattern

Recall from previous lecture:

IController controller = new Controller(model, view);

public interface IViewListener {
    //you can also pass in arguments for the data if you want
    void handleSaveEvent();
    void handleLoadEvent();
    void handleToUpperCaseEvent();
}

public class Controller implements IController, IViewListener {

    public Controller(...) {
        this.view.addViewEventListener(this);
    }
    
    @Override
    public void handleToUpperCase() {
        String text = view.getData();
        view.setData(text.toUpperCase());
    }
    
    @Override
    public void handleSaveEvent() {
        String text = view.getData();
        model.writeData(text);
    }
    
    @Override
    public void handleLoadEvent() {
        String text = model.getData();
        view.setData(text);
    }
    
}

public interface IView {
    String getData();
    void setData(String data);
    void addViewEventListener(IViewListener listener);
    void requestViewFocus();
}
public class MyWindow implements IView, ActionListener, MouseListener, KeyListener {
    List<IViewListener> listeners;

    public MyWindow(IViewListener listener) {
        toUpperCaseButton.addActionListener(this);
        saveButton.addActionListener(this);
        loadButton.addActionListener(this);
        
        this.addKeyListener(this);
    }
    
    public void mouseClicked(MouseEvent e) {
        if (e.getSource() == imageLabel2) {
            emitToUpperCaseEvent();
        }
        
        if (e.getSource() == imageLabel) {
            
        } else if (e.getSource() == this) {
            if (e.getButton() == MouseEvent.BUTTON1) {
                emitSaveEvent();
            } //etc.
        }
    }
    
    public void addViewEventListener(IViewListener listener) {
        this.listeners.add(listener);
    }
    
    public void requestViewFocus() {
        this.setFocusable(true);
        this.requestFocus();
    }
}

OOD in Other Languages

Introduction to C

#include <stdio.h>

struct student {
	int age;
	int id;
	//you can pass the address of a function in the struct
	//void *func;
};

// Uncomment the commented code to work with references
//void updateStudentId(struct student* s, int newId) {
void updateStudentId(struct student s, int newId) {
	printf("The address of s in updateStudentId is: %p\n", &s);
	s.id = newId;
	// s->id = newId;
}

int main() {
	struct student s1;
	s1.age = 21;
	s1.id = 1001;
	// The & is an operator that gets the address
	printf("The address of s1 is: %p\n", &s1);
	
	// Below passes in the reference exactly (and works as expected).
	// This is what Java does automatically
	// updateStudentId(&s1, 2001);
	
	updateStudentId(s1, 2001);

	printf("%d, %d Hello world\n", s1.age, s1.id);
	return 0;
}

Returns:

The address of s1 is: 0x7ffc5dd458d0
The address of s in updateStudentId is: 0x7ffc5dd458b0
21, 1001 Hello world

Pointers and Memory Management

Be sure to run your program through valgrind to make sure there are no memory leaks.

Stack memory: removes once done.

Heap memory: memory that stays.

Leaking memory: When the thing in memory is not being used any more.

#include <stdio.h>
#include <stdlib.h>

typedef struct student {
    int id;
    int age;
    // struct student* next; // Copying this will not do a deep copy
}student_t;

void updateAge(student_t* s, int age) {
    printf("The address of s in updateAge is %p\n", s);
    (*s).age = age;
}

// There are two types of memory: stack memory and heap memory
// Everything created in this method will be destroyed after the method returns
// So because this returns the reference of an object that was created locally,
// it will be pointing to nothing...
// student_t* make_student(int age, int id) {
//     student_t s;
//     s.age = age;
//     s.id = id;
//     printf("The address of s: %p\n", &s);
//     // The reason that this sometimes works is because it often doesn't get
//     // to deleting it right after the method call.
//     return &s; // This will cause a UB: Undefined Behavior
// }

student_t* make_student(int age, int id) {
    student_t* s_p = (student_t*) malloc(sizeof(student_t)); 
    //This stays on the heap until the program ends
    s_p->age = age;
    s_p->id = id;
    printf("The address of s: %p\n", s_p);
    return s_p;
}

int main() {
    // student_t s;
    // s.age = 21;
    // s.id = 1001;
    student_t* p_s = make_student(21, 1001);

    // The * says that this variable is of type reference
    // You can modify the values via the address
    int value = 5;
    int* p_value = &value;
    char someChar = 'c';

    //student_t* p_s = &s;
    
    // The & operator gives the address of the variable
    //
    // &value = gives you the address of where value is in memory
    // *p_value = gives you the object at the address p_value
    (*p_s).age = 45;
    printf("The address of s in memory is %p\n", p_s);

    //updateAge(&s, 23);

    printf("age: %d, id: %d\n", p_s->age, p_s->id);

    free(p_s); // This frees the memory from heap
    // You can only free things that are on the heap

    return 0;
}

Pipeline

There is no overloading is C.

Preprocessor - Fancy RegEx (string substitution). Copies everything needed into the same ‘file’

Example code:

//#include <stdio.h> // This means library
#include "sum.h" // This means local file

#define LINUX 1

int main() {
    
    // The preprocessor handles this so that you don't
    // have to compile unnecessary code
    #if LINUX==1
        sum(1, 2);
    #else
        sum(2, 3);
    #endif

    int result = sum(1, 2);
    return 0;
}

Preprocessor result:

# 1 "example3.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "example3.c"

# 1 "sum.h" 1
int sum(int a, int b) {
    return a + b;
}
# 3 "example3.c" 2



int main() {

        sum(1, 2);
        
    int result = sum(1, 2);
    return 0;
}

Compilation: syntax analysis, tokenisation. This produces an assembly file

Creates object file.

Resulting assembly:

	.file	"preprocesses.c"
	.text
	.globl	sum
	.type	sum, @function
sum:
.LFB0:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	sum, .-sum
	.globl	main
	.type	main, @function
main:
.LFB1:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movl	$2, %esi
	movl	$1, %edi
	call	sum
	movl	$2, %esi
	movl	$1, %edi
	call	sum
	movl	%eax, -4(%rbp)
	movl	$0, %eax
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	 1f - 0f
	.long	 4f - 1f
	.long	 5
0:
	.string	 "GNU"
1:
	.align 8
	.long	 0xc0000002
	.long	 3f - 2f
2:
	.long	 0x3
3:
	.align 8
4:

Linker: turns the object code into the executable

C++

C++ is an extension of C. That is, you can do everything in C in C++

To write to streams, use >>. This is similar to piping in the command line: cat * > file.

Pointers and references

#include <iostream>
#include <vector> // This is equivalent to an ArrayList in Java
#include <string>
#include <assert.h>
// By doing this, you don't have to specify std::
// using namespace std;
//
//

class Shape {
    protected:
        int w;
        int h;
    public:
        void foo() {}
        // The = 0 means that it must be implemented 
        virtual int bar() = 0;
};

// class Oval : public Shape {
// public:
// 
// 
// }

// You can have public, private, or protected inheritance

// You can also have multiple inheritance 
// class Rectangle : public Shape, Oval {
class Rectangle {
private:
    int width;
    int height;
    std::string id;
public:
    Rectangle() {
        // This is similar to the builder pattern
        std::cout << "Rectangle()" << std::endl;
        this->width = 0;
        this->height = 0;
        this->id = "unknown";
    }
    Rectangle(int width, int height, std::string id) {
        std::cout << "Rectangle(int width, int height)" << std::endl;
        this->width = width;
        this->height = height;
        this->id = id;
    }
    // Saying const means that you cannot mutate the object
    Rectangle(const Rectangle& other) {
        this->width = other.width;
        this->height = other.height;
        this->id = other.id;
    }

    int getWidth() const { return this->width; }
    int getHeight() const { return this->height; }
    void setWidth(int w) { this->width = w; }
    void setHeight(int h) { this->height = h; }

    /*
    int bar() {
        return 0;
    }
    */


    // This is called operator overriding 
    // This returns a reference so you can chain them together
    Rectangle& operator=(const Rectangle& other) {
        this->height = other.getHeight();
        this->width = other.getWidth();
        // this->id = other.getId();
        return *this;
    }

    bool operator==(const Rectangle& other) {
        return this->width == other.width && this->height == other.height;
    }

    bool operator<(const Rectangle& other) {
        return this->width < other.width;
    }

    Rectangle operator+(const Rectangle& other) {
        Rectangle result(this->width + other.width, this->height + other.height, this->id + " " + other.id);
        return result;
    }

    ~Rectangle() {
        std::cout << "~Rectangle()" << this->id << std::endl;
        // remember to delete the pointers. Consider a field `int* data;`:
        // delete this->data; 
    }

    // If one class is a friend of another class, it can access their private fields
    // This breaks encapsulation, however
    
    // friend class Oval;
};



// The & means that the object is a reference
void printRectangle(const Rectangle& r) {
    // If this is a pointer, then you have to check for nulls
    //assert (r != NULL) 
    // If it is a reference, then it is already guaranteed to not be NULL
    std::cout << r.getWidth() << " " << r.getHeight() << std::endl;
}

template <class T>
class SmartPtr {
private:
    T* data;
    int* counter;
public:
    SmartPtr(T* data) {
        assert (data != NULL);
        this->data = data;
        this->counter = new int(1);
    }

    SmartPtr(SmartPtr& other) {
        *(other.counter) = *(other.counter) + 1;
        this->counter = other.counter;

        if (*(this->counter) == 0) {
            assert(data != NULL);
            delete this->data;
        }

        this->data = other.data;
    }

    SmartPtr& operator=(SmartPtr& other) {
        *(other.counter) = *(other.counter) + 1;
        this->counter = other.counter;
        this->data = other.data;
    }

    ~SmartPtr() {
        *(this->counter) = *(this->counter) - 1;
        if (*(this->counter) == 0) {
            assert(data != NULL);
            delete this->data;
        }
    }
    
    T& operator*() {
        return *(this->data);
    }

    T* operator->() {
        return this->data;
    }

};

int main(int argc, const char* argv[]) {
    /*
    std::string rect("rect");
    Rectangle r1;
    Rectangle r2(10, 10, rect);
    // Rectangle r1 = new Rectangle(); //new is the same as malloc -- so it returns a reference
    // cout is a Stream
    // std is a namespace (called package in Java)
    // std::cin >> value;
    std::cout << "Hello world" << std::endl;
    printRectangle(r1);
    //printRectangle(r2);
    */

    SmartPtr<Rectangle> s_r1(new Rectangle(10, 10, "r1"));

    // Don't use raw pointers in C++ (don't do the line below)
    Rectangle* r2 = new Rectangle();

    printRectangle(*r2);
    printRectangle(*s_r1);

    std::cout << "Hello world" << std::endl;

    // SmartPtr has already been implemented in C++, look up unique_ptr

    // This causes a double delete if you don't override the = operator 
    // Because they are sharing the same data (shallow assignment)
    SmartPtr<Rectangle> s_r2 = s_r1;
    s_r2->setWidth(20);
    std::cout << s_r1->getWidth()<<std::endl;

    /*
     * Although the counter works, the following code will double free
    int* i = new int(3);

    SmartPtr<int> s_int1(i);
    SmartPtr<int> s_int2(i);
    */

    return 0;
}

This results in:

Rectangle(int width, int height)
Rectangle()
0 0
10 10
Hello world
20
~Rectangle()r1

Exam 2 Review

Question 1

Write tests

Question 2

Implement Extra Credit:

package initial;

import java.util.HashMap;
import java.util.Map;

public class ExtraCreditGradeSchema implements GradeSchema {

  private GradeSchema delegate;
  private Map<String, Double> extraCreditWeights;

  public ExtraCreditGradeSchema() {
    this(new SimpleGradeSchema());
  }

  public ExtraCreditGradeSchema(GradeSchema delegate) {
    if (delegate == null) {
      throw new IllegalArgumentException("Delegate cannot be null");
    }
    this.delegate = delegate;
    this.extraCreditWeights = new HashMap<>();
  }

  @Override
  public void addGradeableItem(String name, double weight) throws IllegalArgumentException {
    if (name == null) {
      throw new IllegalArgumentException("Name cannot be null");
    }

    if ((weight < 0) || (weight > 100)) {
      throw new IllegalArgumentException("Invalid weight, must be between 0 and 100)");
    }

    String[] words = name.split("\\s+");
    for (String s : words) {
      if (s.equalsIgnoreCase("extra")) {
        if (this.extraCreditWeights.containsKey(name)) {
          throw new IllegalArgumentException("Item with given name already exists.");
        }
        this.extraCreditWeights.put(name, weight);
        return ;

      }
    }

    this.delegate.addGradeableItem(name, weight);
  }

  @Override
  public double getWeight(String name) throws IllegalArgumentException {
    if (this.extraCreditWeights.containsKey(name)) {
      return this.extraCreditWeights.get(name);
    }

    return this.delegate.getWeight(name);
  }

  @Override
  public double getWeightedTotal(Map<String, Double> score) throws IllegalArgumentException {
    if (score == null) {
      throw new IllegalArgumentException("Scores cannot be null");
    }
    double extraCredit = 0.0;
    Map<String, Double> nonExtraCredit = new HashMap<>(score);
    for (Map.Entry<String, Double> item : this.extraCreditWeights.entrySet()) {
      if (!score.containsKey(item.getKey())) {
        throw new IllegalArgumentException(
            "No entry in student score for item " + item.getKey());
      }
      extraCredit += item.getValue() * score.get(item.getKey());
      nonExtraCredit.remove(item.getKey());
    }

    return this.delegate.getWeightedTotal(score) + extraCredit / 100;
  }

  @Override
  public double getSumOfWeights() {
    return this.delegate.getSumOfWeights();
  }
}

Question 3

public interface IMultiGradeSchema extends GradeSchema {
    void addGradeableItems(doule totalWeights, String... items);
}

public class MultipleGradeSchema extends SimpleGradeSchema implements IMultiGradeSchema {
    public void addGradeableItems(doule totalWeights, String... items) {
        if (items == null) {
            throw new IllegalArgumentExceptions("Items cannot be null");
        }
        
        for (String item : items) {
            if (item == null) {
                throw new IllegalArgumentException();
            }
        }
        
        if (items.length == 0) {
            throw new IllegalArgumentException("Item size cannot be 0");
        }
        
        if (totalWeight < 0  || totalWeight > 100) {
            throw new IllegalArgumentException("Total weight is out of bounds");
        }
        
        double individualWeight = totalWeights / items.length;
        
        for (String item : item) {
            super.addGradeableItem(item, individualWeight);
        }
        
    }
}

Question 4

public interface ICourse {
    // throws if id is null, "", or already present
    void addStudent(String id, String name) throws IllegalArgumentException;
    // throws if id is null or not a student 
    void withdrawStudent(String id);
    // todo: do throws for these methods as well
    void addGradeableItem(String id, String assignmentName);
    void enterGrade(String id, String assignmentname, double grade);
}

Testing a GUI

How to test GUIs:

You don’t really have to make a mock controller, just make your actual controller take in the mock view.

Make a mock view

public class MockView implements IView {
    private final List<IViewListener> listenres;
    private final List<ActionListener> actionListenres;
    
    public MockView(List<IViewListener> listeners, List<ActionListeners> actionListeners) {
        this.listernes = listenres;
        this.actionListeners = actionListeners;
    }
    
    private IViewListener listener;
    
    public Strng getData() {
    }
    
    public void setData(String data) {
        try {
            this.out.append(data):
        } // catch IOException
    }
    
    public void addViewEventListener(IViewListener listener) {
        this.listner = listener;
    }
    
    public void requestViewFocus() {
        listener.handleLoadEvent();
    }
    
    public void emitToUpperCaseEvent() {
    }
}

public class MockController implements IController, IViewListener {
    private final Appendable out;
    public MockController(Appendable out) {
        this.out = out;
    }
    
    public void run() {}
    
    public void handleSaveEvent() {
        try {
            this.out.append("handleSaveEvent");
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to write to appendable");
        }
    }
    
    public void handleLoadEvent() {
        try {
            this.out.append("handleLoadEvent");
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to write to appendable");
        }
    }
    
    public void handleToUpperCaseEvent() {
        try {
            this.out.append("handleToUpperCaseEvent");
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to write to appendable");
        }
    }
}

// tests

public class MockViewTests {
    public void testSaveButtonClick() {
        MockView mockView = new MockView();
        Appendable out = new StringBuilder();
        IController controller = new MockController(out);
        mockView.addViewEventListener(controller);
        mockView.emitSaveEvent();
        assertEquals("handledSaveEvent", out.toString());
    }
}