An “Iceberg Class” is loosely defined as a class with more private than public methods, but we will be specifically talking about those classes with only one or two public methods (other than an initializer).
Besides letting you get away with not testing most of the methods, Iceberg Classes also benefit from being much more likely to have only a single responsibility, tend to be extremely easy to read, and are easy to refactor.
When a class only has a single method as its interface, it becomes more difficult—though not impossible—for it to be in violation of the Single Responsibility Principle. Hints that you may be violating SRP in an Iceberg Class include use of conditionals, different types of return values, too many public methods, and long methods.
Speaking of long methods, I find that one of the big reasons to write an Iceberg Class is to extract methods that are short with names that are so descriptive that you don’t even need to read their bodies to know what is happening:
class Registration
def initialize(attributes = {})
@user = User.new(attributes)
end
def save
user.valid? && create_user
end
private
attr_reader :user
def create_user
set_integration_token
agree_to_terms_of_service
join_default_groups
save_user
end
# ...
end
The details you cannot see, cannot be used directly in other classes. Therefore it is much easier to change the private part of the class than the interface part of the class. This is important because many classes are likely to change a lot during their lifetimes.
Refactoring Iceberg classes is quite easy, since most of the content of the
class is ‘below the fold’ of the private
keyword, which means that most of the
time you won’t need to make changes outside of the class as a part of the
refactor.
This is because the private methods should never be accessed from outside the class, making it is safe to refactor within that class without worrying about breaking other classes (which only use the public interface) or tests (which should not be covering private portions of the class directly).
The Iceberg Class pattern works best in classes which are Service Objects or
which model business practices, such as FriendshipCreator
, ArticleImporter
,
or Search
. It is not suited to value objects, database models, etc.
It is important when building classes to avoid replicating logic or leaving
behavior that belongs in a value object. Duplicated logic should be extracted
into service objects, such as the logic to find the dom_id
of an object
(user_23
or post_12
). Also consider creating value objects so that logic
such as GeocodingClass#calculate_distance_between(x, y)
can be organized with
the data it operates on: Point#distance_to(point)
.
Iceberg Classes provide a great way to write code that requires a minimum of tests, is self-documented, and is simple to refactor. There are a few things to be wary of, such as that you are hiding behavior which belongs elsewhere and that you are not using the pattern where it doesn’t make sense. I encourage you to give Iceberg Classes a try, and let me know how it works for you.