Objects vs Functions Functional objectoriented style and design

Objects vs. Functions Functional, object-oriented style, and design patterns

Object-oriented vs. non-object-oriented style

Is this object-oriented code? class Rect { constructor(x, y, w, h) { this. x = x; this. y = y; this. width = w; this. height = h; this. kind = 'rect'; } } class Circle { constructor(x, y, r) { this. x = x; this. y = y; this. radius = r; this. kind = 'circle'; } } ● ● ● function in. Shape(shape, x, y) { if (shape. kind === 'rect') { return x > shape. x && x < shape. x + shape. width && y > shape. y & y < shape. y + shape. height; } else if (shape. kind === 'circle') { let x 1 = x - shape. x; let y 1 = y - shape. y; let r = shape. radius; return x 1 * x 1 + y 1 * y 1 < r * r; } else { console. log('ERROR: unknown shape'); } } Why not? It uses classes/objects, so it must be object-oriented, right? in. Shape is a function, not a method. Methods are the essence of object-oriented style. Can you refactor this program into good object-oriented style? (Answer on next slide, but try it yourself first. )

The object-oriented approach class Rect { constructor(x, y, w, h) { this. x = x; this. y = y; this. width = w; this. height = h; } class Circle { constructor(x, y, r) { this. x = x; this. y = y; this. radius = r; } in. Shape(x, y) { let x 1 = x - this. x; let y 1 = y - this. y; let r = this. radius; return x 1 * x 1 + y 1 * y 1 < r * r; } in. Shape(x, y) { return x > this. x && x < this. x + this. width && y > this. y && y < this. y + this. height; } } } What has changed? 1. this instead of the shape argument. (this is an implicit argument to all methods. ) 2. We don’t need the if statement in in. Shape. Why not? Dynamic dispatch. 3. The non-OO approach throws an exception if shape is not a shape. The OO approach throws an exception if you invoke. in. Shape on a non-shape.

Using in. Shape to draw Shapes! for (let i = 0; i < 10000; ++i) { let x = Math. random() * W; let y = Math. random() * H; let in. Shape = s. reduce(function(in. Shape, shape) { return in. Shape || shape. in. Shape(x, y); }, Note: false); ● The Shapes classes don't even have if (in. Shape) { drawing procedures! c. draw. Circle(x, y, 5, [1, 0, 0]); ● This drawing code knows almost Nothing about shapes } else { ● It will also work with new shapes defined after the fact! c. draw. Circle(x, y, 5, [0, 0, 1]); } }

Drawing Shapes (Full Program Listing) class Rect { constructor(x, y, w, h) { this. x = x; this. y = y; this. width = w; this. height = h; } in. Shape(x, y) { return x > this. x && x < this. x + this. width && y > this. y && y < this. y + this. height; } } class Circle { constructor(x, y, r) { this. x = x; this. y = y; this. radius = r; } in. Shape(x, y) { let x 1 = x - this. x; let y 1 = y - this. y; let r = this. radius; return x 1 * x 1 + y 1 * y 1 < r * r; } } let s new new ]; = [ Rect(20, 75, 60, 250), Circle(225, 100), Circle(450, 225, 100) const W = 600; const H = 400; const c = lib 220. new. Canvas(W, H); for (let i = 0; i < 10000; ++i) { let x = Math. random() * W; let y = Math. random() * H; let in. Shape = s. reduce(function(in. Shape, shape) { return in. Shape || shape. in. Shape(x, y); }, false); if (in. Shape) { c. draw. Circle(x, y, 5, [1, 0, 0]); } else { c. draw. Circle(x, y, 5, [0, 0, 1]); } }


Which API is better? let rect = new Rect(0, 0, 10, 200); rect. in. Shape(20, 30); in. Shape(rect, 20, 30); This is completely subjective. ● If you like the OO API, it is probably because you learned Java for a year. ● If you prefer the non-OO API, maybe you also enjoy C from 230. So, let’s ask a different question.

Q: Would you rather re-use libraries that others have written, or re-write everything from scratch, all the time? [Hint: There is one correct answer, but several reasons for the correct answer]



Libraries are an important part of programming. Without libraries, we’d be writing a lot of basic functionality from scratch. In many situations, the availability of libraries determines which programming language you will use. Libraries let you reuse code that others have written.

Extending Libraries It is common to find good libraries that don’t have exactly what you need. In other words, a library author cannot anticipate everything that anyone will want in the future. So, good libraries also need to be extendable or adaptable.

Extending Libraries It is common to find good libraries that don’t have exactly what you need. In other words, a library author cannot anticipate everything that anyone will want in the future. So, good libraries also need to be extendable or adaptable. ● ● Sometimes you can modify the library yourself (e. g. , if you wrote it), There are many situations where you cannot. E. g. , the library may not be open source, or it may be a really big and complicated library, and you may not want to spend time to understand how it is implemented.

Extending Libraries Of Shapes Imagine that you needed a library of shapes, and you found two libraries that have exactly the same features, but one uses an OO approach and the other does not. Which library is more extendable or adaptable?

Two Kinds of Extensions New data types New operations Rectangle Circle Square in. Shape grow We are going to consider both libraries (OO and non-OO) and consider both kinds of extensions.

OOP: Adding New Data Types class Square { constructor(x, y, side) { this. x = x; this. y = y; this. side = side; } in. Shape(x, y) { return x > this. x && x < this. x + this. side && y > this. y && y < this. y + this. side; } } ● ● We simply create a new class with an in. Shape method In a statically typed language, such as Java, we would have a common interface for all classes. In Java. Script, there is no explicit interface.
![Updated Shape-Drawing let s new new ]; = [ Rect(20, 75, 60, 250), Circle(225, Updated Shape-Drawing let s new new ]; = [ Rect(20, 75, 60, 250), Circle(225,](http://slidetodoc.com/presentation_image_h2/0dabfb52c5ffb1b118dc95dccf108254/image-18.jpg)
Updated Shape-Drawing let s new new ]; = [ Rect(20, 75, 60, 250), Circle(225, 100), Square(350, 125, 200) Added a new shape!! for (let i = 0; i < 10000; ++i) { let x = Math. random() * W; let y = Math. random() * H; let in. Shape = s. reduce(function(in. Shape, shape) { return in. Shape || shape. in. Shape(x, y); }, false); if (in. Shape) { c. draw. Circle(x, y, 5, [1, 0, 0]); } else { c. draw. Circle(x, y, 5, [0, 0, 1]); } } Unchanged!!


OOP: Adding New Operations class Rect { constructor(x, y, w, h) { this. x = x; this. y = y; this. width = w; this. height = h; } in. Shape(x, y) { return x > this. x && x < this. x + this. width && y > this. y & y < this. y + this. height; } grow(factor) { return new Rect(x, h, w * factor, h * factor); } } We are not allowed to do this. The plan was to add an operation without modifying the library. (We can add a grow method to a new class, such as Square. )

OOP: Adding New Operations // Unchanged class Rect { constructor(x, y, w, h) { this. x = x; this. y = y; this. width = w; this. height = h; } in. Shape(x, y) { return x > this. x && x < this. x + this. width && y > this. y & y < this. y + this. height; } } function grow(shape, factor) { if (shape. kind === 'rect') { return new Rect(shape. x, shape. h, shape. w * factor, shape. h * factor); } else if (shape. kind === 'circle') {. . . } else if (shape. kind === 'square') {. . . } } Does this approach work? We do not have a. kind field! That was in the non-OO version.

OOP: Adding New Operations // Unchanged class Rect { constructor(x, y, w, h) { this. x = x; this. y = y; this. width = w; this. height = h; } in. Shape(x, y) { return x > this. x && x < this. x + this. width && y > this. y & y < this. y + this. height; } } function grow(shape, factor) { if (shape instanceof Rectangle) { return new Rect(shape. x, shape. h, shape. w * factor, shape. h * factor); } else if (shape instanceof Circle) {. . . } else if (shape instanceof Square) {. . . } } What if we use instanceof instead?

Consensus that instanceof is a bad idea

Interlude: Why instanceof is usually a bad idea 1. In a typed language (i. e. , not in Java. Script), instanceof (and casting) defeat the type system. Working “against” a language leads to errors and unreadable code. 2. In any programming language: a. There may be several implementations of the same kind of object. E. g. , we used width and height for rectangles, but you can also give the coordinates of two corners. The instanceof operator will not help when this occurs. b. You need to know the library internals: Libraries often maintain internal representations not exposed via the public API c. An update in the library may leave your extension incomplete, or worse, undefined

OOP: Adding New Operations by Inheritance class Rect. With. Grow extends Rect { constructor(x, y, w, h) { super(x, y, w, h); } grow(factor) { return new Rect. With. Grow(x, h, w * factor, h * factor); } } ● ● This can work, if we have to be careful to never call new Rect and always call new Rect. With. Grow. What if the library has a call to new Rect?

OOP: Adding New Operations by Inheritance class Rect { constructor(x, y, w, h) { this. x = x; this. y = y; this. width = w; this. height = h; } library class Rect. With. In. Shape extends Rect { constructor(x, y, w, h) { super(x, y, w, h); } in. Shape(x, y) { return x > this. x && x < this. x + this. width && y > this. y && y < this. y + this. height; } grow(factor) { return new Rect(x, h, w * factor, h * factor); } } } let rect = new Rect. With. In. Shape(0, 0, 100); rect. in. Shape(150, 150); // This works. rect. grow(2). in. Shape(150, 150); // error! extension

OOP: Adding New Operations by Wrapping class Rect. With. Grow { constructor(x, y, w, h) { this. rect = new Rect(x, y, w, h); } grow(factor) { return new Rect. With. Grow(x, h, w * factor, h * factor); } in. Shape(x, y) { return this. rect. in. Shape(x, y); } } ● ● You have to create a wrapper for every method. In a large library, there may be dozens or hundreds of methods. In a typed language, if fields are private, wrapping may not be possible.

Summary: 1. Extending an object-oriented library with a new data type is easy. 2. Extending an object-oriented library with new operations is hard.

Non-OOP: Adding New Operations function grow(shape, factor) { if (shape. kind === 'rect') { return new Rect(shape. x, shape. h, shape. w * factor, shape. h * factor); } else if (shape. kind === 'circle') { return new Circle(shape. x, shape. y, shape. radius * factor); . } } Easy! Unlike adding an operation in objectoriented style, which was really hard.

Non-OOP: Adding New Data Types class Square { constructor(x, y, side) { this. x = x; this. y = y; this. side = side; this. kind = 'square'; } } function in. Shape 2(shape, x, y) { if (shape. kind === 'square') { return x > this. x && x < this. x + this. side && y > this. y & y < this. y + this. side; } else { return in. Shape(shape, x, y); } } Adding the new data type is easy, but we also have to wrap the existing operations. i. e. , we have to be careful to call in. Shape 2 instead of the in. Shape function in the library.

Non-OOP: Recursive Data Structures class Overlay { constructor(over, under) { this. over = over; this. under = under; this. kind = 'overlay'; } } function in. Shape(shape, x, y) {. . . else if (shape. kind === 'overlay') { return in. Shape(shape. over, x, y) || in. Shape(shape. under, x, y); } } Suppose the library supported overlaying shapes on top of each other, using the code on the left. Therefore, the in. Shape function (in the library) has a case for overlays too. Could we define the in. Shape 2 wrapper for this library? No! The problem is that the in. Shape calls itself, and we can’t change that call. How do you solve this problem? You can’t. You have to edit the function. (Which may not be possible iif the library is closed source. )

Summary: Writing Extensible Code New data types: easy with objects, hard without objects New operations: hard with objects, easy without objects Rectangle Circle Square in. Shape grow Q: Is there an approach that makes both kinds of extensions easy? A: No. The problem seems unsolvable.
![Fluent Filter vs. Filter let a = [1, 2, 3, 4, 5]; let f Fluent Filter vs. Filter let a = [1, 2, 3, 4, 5]; let f](http://slidetodoc.com/presentation_image_h2/0dabfb52c5ffb1b118dc95dccf108254/image-33.jpg)
Fluent Filter vs. Filter let a = [1, 2, 3, 4, 5]; let f = new Fluent. Filter(a); let a 2 = f. greater. Than(2). less. Than(4). divisible. By(3). get() let a = [1, 2, 3, 4, 5]; let a 2 = a. filter(function(x) { return x > 2; }). filter(function(x) { return x < 4; }). filter(function(x) { return x % 3 === 0; })
![Fluent Filter vs. Filter let a = [1, 2, 3, 4, 5]; let f Fluent Filter vs. Filter let a = [1, 2, 3, 4, 5]; let f](http://slidetodoc.com/presentation_image_h2/0dabfb52c5ffb1b118dc95dccf108254/image-34.jpg)
Fluent Filter vs. Filter let a = [1, 2, 3, 4, 5]; let f = new Fluent. Filter(a); let a 2 = f. greater. Than(2). less. Than(4). divisible. By(3). get() ● ● Concise; readable; Requires support code Specialized Reusable with minimal duplication let a = [1, 2, 3, 4, 5]; let a 2 = a. filter(function(x) { return x > 2; }). filter(function(x) { return x < 4; }) requires parsing ● Verbose; . filter(function(x) ● No support code required { return x % 3 === 0; ● General-purpose ● Reusable }) requires duplication

Where Do You Encounter Repeated Filtering Operations In the Real World?


Project 4 ● Working with real-world data!! ○ Subset of the Yelp restauarant dataset ● Coping with real-world data ○ ○ ○ Missing / optional fields Large size Variable structure ● Task: Implement a Fluent filtering class > console. log(f. rating. Leq(4). rating. Geq(2). category('Restaurants'). has. Ambience('casual'). from. State('AZ'). best. Place(). name); < Pappadeaux Seafood Kitchen
- Slides: 37