Инкапсуляция нужна не для защиты самого кода (вы правильно сказали - всё всегда можно увидеть), а для того, чтобы защитить код, использующий класс, от изменений этого класса. Есть такие два понятия - интерфейс и реализация. У любого класса должно быть назначение (и желательно - только одно назначение), и те элементы, относящиеся к назначению, делают публичным. А те элементы, которые не связаны с самой задачей и предназначены для вспомогательных функций, то это закрытые элементы, пользователю этого класса они не нужны.
Пример. Есть задача - найти корни квадратного уравнения. Основной функцией является получение корней. Обычно это решается с помощью дискриминанта, но не обязательно, есть и другие способы, поэтому, дискриминант сам по себе не нужен, ведь дискриминант - всего лишь способ решения (реализация).
Как пример, пусть будет такой класс для решения квадратного уравнения:
class KvadrUr
{
public double A { get; set; }
public double B { get; set; }
public double C { get; set; }
public bool HasRoots { get { return GetDiscr() >= 0; } }
public KvadrUr()
{
}
public KvadrUr(double a, double b, double c)
{
A = a;
B = b;
C = c;
}
public double[] GetRoots()
{
var discr = GetDiscr();
if (discr < 0) return new[] {double.NaN, double.NaN};
var sqrt = Math.Sqrt(discr);
return new[]
{
(-B + sqrt) / (2 * A),
(-B - sqrt) / (2 * A)
};
}
private double GetDiscr()
{
return B*B - 4*A*C;
}
}
В данном классе, есть интерфейс - данные (коэффициенты) и два метода (список корней и наличие корней). А метод вычисления дискриминанта - для задачи не нужно.
А теперь представьте, что получение дискриминанта будет публичным методом. Тогда кто-нибудь возьмёт и начнёт использовать этот метод в своём коде. И если автор класса захочет и изменит реализацию на другой способ (по формуле Виета, например), то дискриминант уже будет не нужен, и метод GetDiscr() нужно будет удалить. Но в том месте, где его использовали, будет ошибка - метод GetDiscr() не существует.
Таким образом, если в классе есть точное указание, что является интерфейсом, а что реализацией, даёт коду больше возможности на изменение этого кода.
Во многих языках программирования имеется особенная возможность, связанная с интерфейсами. В C# это одноимённое понятие - interface. В данном примере хорошо бы создать такой интерфейс:
interface IRoots
{
bool HasRoots { get; }
double[] GetRoots();
}
class KvadrUr : IRoots
{
// ...
}
Можно создать несколько разных классов - один считает через дискриминант, другой - через формулу Виета, третий - через выделение полного квадрата, четвёртый - кубическое уравнение, пятый - биквадратное уравнение. Но каждый из классов реализуют один и тот же интерфейс, и каждый из них может быть взаимозаменяем.