-
Notifications
You must be signed in to change notification settings - Fork 0
Elegant Objects
Thomas Czogalik edited this page Jan 18, 2022
·
3 revisions
#Elegant Objects
- do not name class after what class objects are doing.
- name class after what it is
- class is a Pixel, that can change color, not PixelColorChanger don't
class CashFormatter {
private int dollars;
CashFormatter(int dollar) {
...
}
public String format() {
...
}
}
do
class Cash {
private int dollars;
Cash(int dollar) {
...
}
public String usDollar() {
...
}
}
- have many constructors and less methods
new Cash(30)
new Cash('30')
new Cash(30d)
new Cash(30f)
new Cash(30, 'USD')
- init of object must be code-free -> instead wrap them
class Cash {
private int dollars;
Cash(String dollar) {
this.dollars = Integer.parseInt(dollar)
}
}
class Cash {
private Number dollars;
Cash(String dollar) {
this.dollars = new StringAsInteger(dollar)
}
}
class StringAsInteger implements Number{
private String source;
StringAsInteger(String source) {
this.source = source
}
int intValue() {
return Integer.parseInt(this.source)
}
}
- conversion is delayed until object initialization
- first create object -> second allow it to work for us. Do not mix
App app = new App(new Data(), new Screen())
app.run()
small objects are readable and maintainable
- as litte as possible
- no more than 4 objects
- in many languages state and identity are seperated
- no encapsulation -> similar to static method
- always use interfaces
- no public method without interface, because user couples thightly with object
- builder methods build something and return it (noun)
- manipulator methods modify entity and return void (verb)
- never mix
- no constant classes
- because class does not know what it is (no state, etc.)
class Constant {
public static String Crlf = "\r\n"
}
- use micro classes
class CrlfString {
private String origin;
CrlfString(String src) {
this.origin = src
}
@Override
String toString() {
return String.format("%s\r\n", origin)
}
}
- helps readablity
- no identity mutability:
- compare 2 objects -> change one of them -> you think they are still equal but are not
- failure atomicity: Objects are always complete -> helps thread safty
- side effect free
- do not use null
- Tests > Documentation
- mocks make implementation hard to change
- use fakes instead of mocks
- in production code at best
interface Exchange {
float rate(String origin, String target);
final class Fake implements Exchange {
@Override
float rate(String origin, String target) {
return 1.234;
}
}
}
@Test
void test() {
Exchange exchange = new Exchange.Fake();
Cash dollar = new Cash(exchange, 500);
Cash euro = dollar.in("EUR");
assert "6.17".euqals(euro.toString());
}
- the bigger the class the lower its maintainability
- rather define than give instruction
class Max implements Number {
private final Number a;
private final Number b;
public Max(Number left, Nuimber right) {
this.a = left;
this.b = right;
}
}
// x "is a" maximum between 5 and 9
Number x = new Max(5,9);
// vs static (procedural)
int x = Math.max(5,9)
- imperative: statements that change program state
- Declarative: express logic of computation without describing control flow
- in declarative style user decides when he wants result -> lazy evaluation
- objects trigger code execution
- get and set prefix asume object is data structure
- instead use exceptions, list, arrays, optionals (java)
- use null classes
class NullUser implements User {
private final String label;
NullUser(String name) {
this.label = name;
}
@Override
public String name() {
return this.label;
}
@Override
public void raise(Chash salary) {
// throw exception
}
}
- reveal errors immediately
- if someone uses api wrong he has to handle exception