Skip to content

Instantly share code, notes, and snippets.

@logkcal
Created December 19, 2011 19:55
Show Gist options
  • Save logkcal/1498607 to your computer and use it in GitHub Desktop.
Save logkcal/1498607 to your computer and use it in GitHub Desktop.
SOLID principle to common code

  • S: Single Responsibility Principle
  • O: Open closed principle
  • L: Liskov substitution principle
  • I: Interface segregation principle
  • D: Dependency inversion principle

  • SSR: every object should have a single responsibility and all its services should be narrowly aligned with that responsibility --- to be measured as cohesion.

    • e.g. a responsibility is defined as a reason to change; consider a module that compiles and prints a report, which will change for two reasons; one thing substantive, and the other cosmetic; it would be a bad design to couple two things that change for different reasons at different times.
  • OCP: software modules should be open for extension, but closed for modifications.

  • DIP: high-level modules should not depend on low-level modules; both should depend on abstractions unlike conventional dependency relationships established from high-level modules to low-level modules.

    • The interfaces defining behavior/services required by the high-level component are owned by and exists within the high-level component's package.
    • The implementation of low-level component requires that the low-level component package depends upon the high-level component for compilation.
    • Patterns such as plugin, service locator, or dependency injection facilitate the run-time provisioning of the chosen low-level component implementation.
    • Dependency inversion can also be applied to adapter pattern where the high-level module defines an adapter interface which is the abstraction the high-level class depends on, and then the adaptee implementation depends on the adapter interface from within the low-level module.

Robert C. Martin's principles of object oriented design.

[ Class Hierarchy | Latest Javadoc ]


open questions: why there isn't a checked function?


  • null gotchas

    • Map.get(key) can mean a value is null, or is not in the map by returning hopelessly ambiguous null.
    • null can mean failure, success, or almost anything while using something other than null makes things clear.
    • many Guava utilities are designed to fail fast in the presence of null as 95% of collections shouldn't have null values.
    • Strings.isNullOrEmpty(s), emptyToNull(s), and nullToEmpty(s).
  • null in collections

    • for null keys on a set or a map, explicitly special-case null keys during look ups.
    • for null values on a map, leave out null entries, and keep a separate set of keys for them.
    • a sparse List<E> might be represented by a Map<Integer, E>.
    • a sensible "null object" can be considered, e.g. a null constant within an enum.
    • alternatively, Collections.unmodifiableList(Lists.newArrayList()) instead of ImmutableList.
  • optional as a way of replacing a nullable reference w/ a non-null value.

    • static: Optional.of(T), absent(), and fromNullable(T).
    • non-static: isPresent(), get(), or(T), orNull(), and asSet().
  • preconditions

    • Preconditions.checkArgument, checkState, checkElementIndex, and checkPositionIndex.
    • Preconditions.checkForNull is preferred to JDK7 Objects.requireNonNull.
  • object methods

    • Objects.equals(lhs, rhs), hashCode(Object...), toStringHelper(this).add("x", 1).toString(), and compareTo(that) through comparison chains:

public int compareTo(Foo that) {
    return ComparisonChain.start()
        .compare(this.aString, that.aString)
        .compare(this.anInt, that.anInt)
        .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
        .result();
}
  • ordering
    • factories: natural(), usingToString(), and arbitrary().
    • configurations: reverse(), nullsFirst(), nullsLast(), compound(comparator), lexicographical(), and onResultOf(function).
    • applications: greatestOf(iterable, k), leastOf(iterable, k), isOrdered, softCopy, min(E, E...), and max(E, E...).

Ordering<String> byLength = new Ordering<String>() {
    public int compare(String lhs, String rhs) {
        return Ints.compare(lhs.length(), rhs.length());
    }
};
  • throwables
    • propagate(throwable);
    • propagateIfInstanceOf(throwable, class<e extends Exception>);
    • propagateIfPossible(throwable, class<e extends Throwable>);

  • immutable collections --- as defensive copies, easy-to-use, thread-safe, time & space savings w/o overhead of concurrent modification checks and extra space past capacity.

    • immutableSet.copyOf(set), immutableMap.of("a", 1, "b", 2), ImmutableSet.builder().addAll(iterable).add(element).build().
    • Multiset to count the number of occurrences of an element; alternatively, Map<Key, Integer>.
    • Multimap to keep a set as a value. cf. a traditional way to represent a graph: Map<V, Set<V>>.
    • ListMultimap to keep an ordered list of values associated with a key.
    • SetMultimap to keep an unordered set of values associated with a key.
  • bi-map guarantees values are unique, allowing you to view the inverse BiMap<V, K> with inverse().

  • table

    • rowMap() views a Table<R, C, V> as a Map<R, Map<C,V>> while columnMap() views it as a Map<C, Map<R,V>>.
    • rowKeySet() returns a Set<R>, while columnKeySet() returns a Set<C>.
    • row(r) returns a non-null Map<C, V>, while column(c) returns a non-null Map<R, V>>.
    • cellSet() returns a set of Table.Cell<R, C, V>.
  • ClassToInstanceMap, alternatively, a Map<Class, Object>.

    • methods: T getInstance(Class<T>) and T putInstance(Class<T>, T);
  • factory methods for collection types

    • for JDK collection types w/ raw constructors, factories can be found in Lists, Sets, Maps, and Queues.
    • Guava collection types hides raw constructors, but rather exposes factory methods, e.g. HashMultiset.create().
  • Lists.partition returns consecutive sublists of the same size except the last one, that are unmodifiable live views. Lists.reverse returns a modifiable, write-through view.

  • Sets

    • cartesianProduct(set...), newSetFromMap(map), and powerSet(set)
    • newSetFromMap(Maps.newConcurrentHashMap()) builds a thread-safe set.
    • difference(s1, s2), symmetricDifference(s1, s2), intersection(s1, s2), and union(s1, s2)
  • Maps has uniqueIndex(), difference(), and unmodifiableBiMap().

  • Iterables has concat(), addAll(), elementsEqual(), getOnlyElement(), and cycle().


  • cache
    • applicability --- when a value is expensive to compute or retrieve, and automated population and eviction of values is desirable.
    • population by a cache loader when there is a sensible default function to load a value associated with a key, by a callable, or directly into a map.
      • cacheBuilder.build(new CacheLoader<Key, Graph>() { load(Key key); loadAll(Iterable keys); });
      • cache.refresh(key); // old value is returned while being asynchronously refreshed.
      • cache.get(key, new Callable() { public Graph load(Key key); }
      • cache.asMap().put(key, graph);
    • eviction
      • cacheBuilder.removalListener(notifiableOfCauseKeyAndValueOnRemoval);
      • timed: expireAfterAccess(duration), expireAfterWrite(duration), refreshAfterWrite(duration).
      • max size to evict entries that haven't been used recently or very often.
      • weightier when values have radically different memory footprints.
      • tweaks on GC: weakKeys to store keys using weak references and softValues to wrap values in soft references.
      • invalidate(key), invalidateAll(keys), and invalidateAll().
    • statistics can help significantly in tuning with hit rate, average loading time, and eviction count.

  • functions and predicates
    • trade-offs between traditional imperative way vs. preposterously awkward functional style.
      • net savings of lines of code, and lazily computed views in constant time.
    • special predicates: CharMatcher.forPredicate(predicateOfCharacter), Range.atMost(2) as Predicate<Integer>, and the like.
    • function factories: forMap(), compose(), constant(), identity(), and toStringFunction().
    • predicate factories: instanceOf, assignableFrom, isNull, alwaysFalse, alwaysTrue, contains, in, equalTo, compose, and, or, and not.
    • filtering and manipulating collections & iterables.
      • Multimaps.index(employees, jobCategoryOf); // returns an immutable map keyed by job categories.
      • Maps.uniqueIndex(employees, employeeIdOf); // returns an immutable multi-map keyed by employee ids.

  • strings
    • Joiner instances are always immutable, and configuration methods return new joiners, that are thread-safe, and fit for static final constants.
      • Joiner.on("; ").skipNulls().join("Harry", null, "Ron", "Hermione"); // or, useForNull("null");
    • Splitter.split returns an iterable of strings under complete control, and is set to work on any pattern, char, string, or char-matcher.
      • Splitter.on(',').trimResults().omitEmptyStrings().split("foo,bar,, qux");
    • CharMatcher has static finals for most needs, and can be commonly obtained by anyOf(), is(), inRange(), negate(), and(), and or().
      • collapseFrom, matchesAllOf, removeFrom, retainFrom, trimFrom, and replaceFrom.

  • primitives
    • array & general utilities, byte conversion methods such as from & toByteArray, and unsigned support w/ parseUnsigned, valueOf, and toString.

  • ranges

    • basics: contains(), containsAll(), lowerEndPoint(), upperEndPoint(), and isEmpty().
    • relations: encloses(), isConnected(), intersection(), and span().
      • asSet() makes equal views of sets for ranges [2..4] and (1..5).
      • canonical() makes equal views of ranges when their sets are equal.
  • discret domains

    • non-static: distance(), maxValue(), minValue(), next() and previous().

  • hashing
    • Object#hashCode tends to be very fast w/ weak collision prevention, but suitable for use in hash tables w/ a light performance hit on collisions. Beyond simple hash tables, there are many uses of hash functions, e.g. a bloom filter.

HashFunction hf = Hashing.md5();
HashCode hc = hf.newHasher()
    .putLong(id)
    .putString(name)
    .putPerson(person, personFunnel)
    .hash();

new Funnel<Person>() {
    @Override
    void funnel(Person person, Sink into) {
        into
          .putInt(person.id)
          .putString(person.firstName)
          .putString(person.lastName)
          .putInt(birthYear);
    }
};

  • math: exhaustive overflow checks, optimized with benchmarks, and designed to be readable.

  • ListenableFuture that extends Future that holds the result of an asynchronous operation.

    • creating listenable futures
      • ListeningExecutorService.submit(callable), ListenableFutureTask.create(...), or JdkFutureAdapters.listenInPoolThread(future).
    • adding listeners and callbacks
    • complex chains of asynchronous operations
      • Futures.allAsList(ListenableFuture<V>...);
      • Futures.successfulAsList(ListenableFuture<V>...);
      • Futures.transform(rowKeyListenableFuture, queryFunction, queryExecutor);
      • Futures.transform(rowKeyListenableFuture, asyncQueryFunction, queryExecutor);
  • CheckedFuture extends ListenableFuture w/ get methods that can throw a checked exception.


  • service
    • AbstractIdleService tends to have startUp(), and shutDown() methods extended while we do not need to perform any action in the "running" state.
    • AbstractExecutionThreadService calls startUp(), invokes run() on a new thread, and calls triggerShutdown() and waits for the thread to die.
    • AbstractScheduledService performs some periodic action by fixed rate, or delay schedule, or a custom scheduler.

protected void startUp() {
    dispatcher.listenForConnections(port, queue);
}
protected void run() {
    Connection connection;
    while ((connection = queue.take() != POISON)) {
        process(connection);
    }
}
protected void triggerShutdown() {
    dispatcher.stopListeningForConnections(queue);
    queue.put(POISON);
}

class CrawlingService extends AbstractScheduledService {
    private Set<Uri> visited;
    private Queue<Uri> toCrawl; 
    protected void runOneIteration() throws Exception {
        Uri uri = toCrawl.remove();
        Collection<Uri> newUris = crawl(uri);
        visited.add(uri);
        for (Uri newUri : newUris) {
            if (!visited.contains(newUri)) { toCrawl.add(newUri); }
        }
    }
    protected Scheduler scheduler() {
        return Scheduler.newFixedRateSchedule(0, 1, TimeUnit.SECONDS);
    }
    protected void startUp() throws Exception {
        toCrawl = readStartingUris();
    }
    protected void shutDown() throws Exception {
        saveUris(toCrawl);
    }
}
  • monitor is designed to provide a synchronization abstraction that supports waiting on arbitrary boolean conditions.

public class SafeBox<V> {
    private final Monitor monitor = new Monitor();
    private final Monitor.Guard valuePresent = new Monitor.Guard(monitor) {
        public boolean isSatisfied() {
            return value != null;
        }
    };
    private final Monitor.Guard valueAbsent = new Monitor.Guard(monitor) {
        public boolean isSatisfied() {
            return value == null;
        }
    };
    private V value;

    public V get() throws InterruptedException {
        monitor.enterWhen(valuePresent);
        try {
            V result = value;
            value = null;
            return result;
        } finally {
            monitor.leave();
        }
    }

    public void set(V newValue) throws InterruptedException {
        monitor.enterWhen(valueAbsent);
        try {
            value = newValue;
        } finally {
            monitor.leave();
        }
    }
}

  • event bus - request-scoped, or at global scope
    • traditional listener interface has a number of disadvantages as follows:
      • one class can only implement a single response to a given event.
      • listener interface method may conflict, and can't be named after its purpose.
      • each event usually requires its own interface even for a family of events.

// typically subscribed by a container class.
class EventBusChangeRecorder {
    @Subscribe void recordChangeInCustomer(ChangeEvent e) {
        recordChange(e.getChange());
    }
}

// typically registered on start-up, or lazy initialization.
eventBus.register(new EventBusChangeRecorder());

// posted much later.
public void changeCustomer() {
    ChangeEvent event = getChangeEvent();
    eventBus.post(event);
}

Juggling as a metaphor for software development --- different roles in the software development process juggle different "trinities"; project and program Managers juggle Features, Resources and Time, and software developers juggle correctness, performance and security.

  • i will bet if you reassign a variable your method does at least two different things.
  • every method should only do one thing according to single responsibility principle.

No setters (and no getters either)

  • do you really need setters for your fields? you'd better create new copies of your objects if values change. try not to write getters either.
  • tell, don't ask --- tell objects what to do; do not ask objects questions about their states, make a decision, and then tell them what to do.

Do not use loops for list operations

List beerDrinkers = filter(filter(persons, canDrinkBeer), isMale);

Use one liners

public int add(int a, int b) { return a + b; }

Use many, many objects with many interfaces.

Use Erlang-Style Concurrency

Use Fluent Interfaces

ConcurrentMap graphs = new MapMaker()
    .concurrencyLevel(32)
    .softKeys()
    .weakValues()
    .expiration(30, TimeUnit.MINUTES)
    .makeComputingMap(new Function() {
        public Graph apply(Key key) {
            return createExpensiveGraph(key);
        }
    });

Data Transfer Objects without getters and setters

public class Point {
    public final int x;
    public final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment