Both of the current answers seem to only partially hit the mark, and they focus on examples that cloud up the core idea. This is also not (solely) an OOP principle but a software design principle in general.
The thing that is "varying" in this phrase is the code. Christophe is on point in saying that it is usually something that may vary, that is you often anticipate this. The goal is to protect yourself from future changes in the code. This is closely related to programming against an interface. However, Christophe is incorrect to limit this to "implementation details". In fact, the value of this advice is often due to changes in requirements.
This is only indirectly related to encapsulating state, which is what I believe David Arno is thinking of. This advice does not always (but often does) suggest encapsulating state, and this advice applies to immutable objects as well. In fact, merely naming constants is a (very basic) form of encapsulating what varies.
CandiedOrange explicitly conflates "what varies" with "details". This is only partially correct. I agree that any code that varies is "details" in some sense, but a "detail" may not vary (unless you define "details" to make this tautological). There may be reasons to encapsulate non-varying details, but this dictum is not one. Roughly speaking, if you were highly confident that "dog", "cat", and "duck" would be the only types you'd ever need to deal with, then this dictum does not suggest the refactoring CandiedOrange performs.
Casting CandiedOrange's example in a different context, assume we have a procedural language like C. If I have some code that contains:
if (pet.type() == dog) {
pet.bark();
} else if (pet.type() == cat) {
pet.meow();
} else if (pet.type() == duck) {
pet.quack()
}
I may reasonably expect that this piece of code will change in the future. I can "encapsulate" it simply by defining a new procedure:
void speak(pet) {
if (pet.type() == dog) {
pet.bark();
} else if (pet.type() == cat) {
pet.meow();
} else if (pet.type() == duck) {
pet.quack()
}
}
and using this new procedure instead of the block of code (i.e. an "extract method" refactoring). At this point adding a "cow" type or whatever only requires updating the speak
procedure. Of course, in an OO language you may instead leverage dynamic dispatch as alluded to by CandiedOrange's answer. This will happen naturally if you access pet
via an interface. Eliminating conditional logic via dynamic dispatch is an orthogonal concern which was part of why I made this procedural rendition. I also want to emphasize that this does not require features particular to OOP. Even in an OO language, encapsulating what varies does not necessarily mean a new class or interface needs to be created.
As a more archetypal example (which is closer to but not quite OO), say we want to remove the duplicates from a list. Let's say we implement it by iterating over the list keeping track of the items we've seen so far in another list and removing any items we've seen. It's reasonable to assume that we may want to change how we keep track of the seen items may for, at least, performance reasons. The dictum to encapsulate what varies suggests that we should build an abstract data type to represent the set of seen items. Our algorithm is now defined against this abstract Set data type, and if we decide to switch to a binary search tree, our algorithm doesn't need to change or care. In an OO language, we may use a class or interface to capture this abstract data type. In a language like SML/O'Caml you might instead capture the Set abstract data type as a module.
For a requirements-driven example, say you need to validate some field with regards to some business logic. While you may have specific requirements now, you strongly suspect that they will evolve. You may encapsulate the current logic in its own procedure/function/rule/class.
Though this is an orthogonal concern which is not part of "encapsulate what varies", it is often natural to abstract out, that is parameterize by, the now encapsulated logic. This typically leads to more flexible code and allows the logic to be changed by substituting in an alternate implementation rather than modifying the encapsulated logic.
I don't know how exactly would it contribute to a better design
Encapsulating details is about loose coupling between the "model" and the implementation details. The less tied is the "model" to the implementation details, the more flexible is the solution. And it makes easier to evolve it. "Abstract yourself from the details".