Princípio: Favorecemos coesão através do encapsulamento

O Básico

O Encapsulamento é basicamente o ato de juntar comportamentos e estados que fazem sentido no mesmo lugar, garantindo maior coesão ao código, é um conceito básico que precisa ser dominado. Veja esse código em um repositório de um framework da Apache

for (Address address : vcard.getAddresses()) {
                    boolean workAddress = false;
                    for (AddressType addressType : address.getTypes()) {
                        if (AddressType.PREF.equals(addressType) || AddressType.WORK.equals(addressType)) {
                            workAddress = true;
                            break;
                        }
                    }
                    if (!workAddress) continue;

Sem entender muito do código e de seu contexto, já somos capaz de refatorar isso de uma maneira melhor, poderíamos simplesmente usar:

if(!adress.hasWorkAdress()) continue;

Com isso, o código escrito na classe ficaria mais legível e a função de descobrir se há endereço de trabalho ou não, passa a ser da classe Adress e pode ser replicado sem problemas através de toda a aplicação. Se decidirmos mudar a regra de negócio no estado atual, teríamos que verificar por esse imenso código esparramado por todo o programa, o que não acontece no código refatorado.

Apenas com essa alteração:

Como podemos detectar isso? Um forte indicativo que algo está estranho é estarmos usando um estado interno e aplicando lógica em cima desse estado interno fora de sua classe.

Métodos privados também podem indicar esse tipo de comportamento, talvez até mesmo a necessidade do nascimento de novas entidades.

private Person createUserAccount(String username, Collection<GrantedAuthority> authorities,PersonAttributesLookup personAttributesLookup) {
		Person person = null;

		if (hasAccountCreationPermission(authorities)) {
			person = new Person();
			person.setEnabled(true);
			person.setUsername(username);

			try {
				// Get the Person Attributes to create the person
				final PersonAttributesResult attr =
						personAttributesLookup.lookupPersonAttributes(username);
				person.setSchoolId(attr.getSchoolId());
				person.setFirstName(attr.getFirstName());
				person.setLastName(attr.getLastName());
				person.setPrimaryEmailAddress(attr.getPrimaryEmailAddress());

				ensureRequiredFieldsForDirectoryPerson(person);
				person = create(person);
				externalPersonService.updatePersonFromExternalPerson(person, false);
				LOGGER.info("Successfully Created Account for {}", username);

			} catch (final ObjectNotFoundException onfe) {
				...
			}

Refatorando:

				person.setSchoolId(attr.getSchoolId());
				person.setFirstName(attr.getFirstName());
				person.setLastName(attr.getLastName());
				person.setPrimaryEmailAddress(attr.getPrimaryEmailAddress());
// passa a se tornar
public Class Person{
	...
	Person setAttributesBasedOnAttributesResult(PersonAttributesResult attr){
				this.person.setSchoolId(attr.getSchoolId());
				this.person.setFirstName(attr.getFirstName());
				this.person.setLastName(attr.getLastName());
				this.person.setPrimaryEmailAddress(attr.getPrimaryEmailAddress());

	}
}
// seria usado:
person.setAttributesBasedOnAttributesResult(attr);

Somente nessa alteração, já travamos setters que podem não ser interessantes para o negócio (como alterar o schoolId sem alterar o emailAdress, se isso for uma regra existente), ou seja, o código antigo estava desprotegido e acoplado mentalmente à alguma regra externa, precisamos sempre que atualizarmos o primeiro nome atualizar também o segundo? não sei. Se sim, podemos fazer com que o encapsulamento garanta isso.