You will usually have a situation where one class uses services of some other class. "uses services" means calls its public methods. For example, CalculatorUser uses MyCalc - CalculatorUser instance will somewhere call MyCalc.Sum().
Imagine now that you give to your customer your application which contains classes CalculatorUser and MyCalc. You can write something like this:
public class CalculatorUser { private MyCalc _myCalc; public CalculatorUser(MyCalc myCalc) { _myCalc = myCalc; } public void PerformAddition(int a, int b) { _myCalc.Sum(a, b); } } class Program { static void Main(string[] args) { MyCalc calculator = MyCalc(); CalculatorUser calcUser = new CalculatorUser(calculator); calcUser.PerformAddition(1, 2); Console.ReadKey(); } }
Everything looks good but then after some time that customer comes back to you with a new requirement: "I want CalculatorUser to have more choice: I want it to be able to choose between old, simple calculator MyCalc and a new, fancy calculator which displays operands, operation and result! Also, this choice has to be made in the runtime."
You realize that now you have to create MyFancyCalc and also to change CalculatorUser in order to support this new requirement. You might want to add another member of type MyFancyCalc to CalculatorUser and then another method PerformAdditionWithFancyCalc() which would use MyFancyCalc. But what if your customer comes with requirement for 10 another types of calculators - would you be adding new members and methods for each of them? If you keep user and service provider tightly coupled, every change in requirements will lead to constant changes in the user and solution to this is having user not to know about particular service provider but only about the services it provides: what are names of those services, what are types of their input values, what are their output types? This is actually what makes the public interface of the service provider. CalculatorUser does not need to know for particular implementation of calculator - MyCalc or MyFancyCalc, all it has to know is that any calculator it uses has a method Sum which accepts two int values and returns void. By this way you are decoupling user from particular calculator and makes it being able to use any calculator which implements Sum in the way described in the interface. If you create MyExtraFancyCalc class you will not need to change CalculatorUser.
So, to satisfy new requirement (making calculator choice in the runtime), you could write something like this:
public interface ICalculator { void Sum(int a, int b); } public class MyCalc : ICalculator { public void Sum(int a, int b) { Console.WriteLine(a + b); } } public class MyFancyCalc : ICalculator { public void Sum(int a, int b) { Console.WriteLine("{0} + {1} = {2}", a, b, a + b); } } public class CalculatorUser { private ICalculator _calculator; public CalculatorUser(ICalculator calculator) { _calculator = calculator; } public void PerformAddition(int a, int b) { _calculator.Sum(a, b); } } class Program { static void Main(string[] args) { bool useFancyCalculator = GetUseFancyCalculator(); ICalculator calculator = CreateCalculator(useFancyCalculator); CalculatorUser calcUser = new CalculatorUser(calculator); calcUser.PerformAddition(1, 2); Console.ReadKey(); } static bool GetUseFancyCalculator() { Console.WriteLine("Would you like to use fancy calculator? (y/n)"); string choice = Console.ReadLine(); return (choice == "y"); } static ICalculator CreateCalculator(bool createFancyCalculator) { ICalculator calculator = null; if (createFancyCalculator) calculator = new MyFancyCalc(); else calculator = new MyCalc(); return calculator; } }
User is asked "Would you like to use fancy calculator? (y/n)" and if types "n", old calculator is used and the output is simply "3" but if answer is "y", fancy calculator is used and output is "1 + 2 = 3"
This example shows the power of interfaces (and basically one simple case of pattern called Dependency Injection).
In the real life you would more often have situation where your customers have an app with consumer (CalculatorUser) which never or very rarely changes and plugins (DLLs) which contain various implementations of the service provider. Main app detects in the runtime which plugins are available and picks one, depending on the user choice (or some other criteria) generated in the runtime.