站内搜索: 请输入搜索关键词
当前页面: 图书首页 > NET For Java Developers Migrating To C#

NET For Java Developers Migrating To C#

[ directory ] Previous Section Next Section

6.1 Basic Interfaces

The word "interface" evokes the image of a boundary where two independent systems interact. In software systems, an interface usually means a contract or a set of rules that systems abide by in order to interact with each other. A person driving a car can be considered two systems (the person and the car) interacting with each other. The person driving the car knows that all cars have a common set of features (steering wheel, gear train, wheels, and so on), and to use the car the driver must interact with those features. Those basic features can be thought of as a contract every car abides by in order to interact with humans. All cars implement an interface that drivers are familiar with.

From an object-oriented programming perspective, an interface represents a collection of methods that an object implements so that other objects can call those methods without knowing their implementation details. A person driving a Volvo or a BMW does not need to know the inner workings of the gear train. The driver simply interacts with the standard gear interface provided by the two cars.

An interface is easy to define. The following ICar interface provides three methods:

interface ICar {
  string DriveTrain();
  string Style();
  string Engine();
}

The Volvo and BMW classes implement the ICar interface and therefore provide implementations for those three methods:

class Volvo : ICar {
  public string DriveTrain() { return Go2Database("DriveTrain"); }
  public string Style() { return Go2Database("Style"); }
  public string Engine() { return Go2Database("Engine"); }
  private string Go2Database (string property) {
  }
}

class BMW: ICar {
  public string DriveTrain() { return ReadFromFile ("DriveTrain"); }
  public string Style() { return ReadFromFile ("Style"); }
  public string Engine() { return ReadFromFile ("Engine"); }
  private string ReadFromFile (string property) {
  }
}

Objects can interact with the BMW and Volvo objects through the ICar interface without knowing what these objects do to retrieve the information returned by the three methods. BMW objects read their properties from a disk file, whereas Volvo objects read their properties from the database. But no matter what the individual objects do to retrieve the information, the caller of these three methods gets a string in return. The caller is aware only of the method signature and not its details. The ICar interface thus facilitates the hiding of implementation details of the Volvo and BMW objects.

An object can implement multiple interfaces and thereby adopt different behaviors. This ability to implement multiple interfaces makes up for the lack of multiple class inheritance in C# and Java (in these languages, a class can inherit from only one class). Although interfaces have only declarations associated with them and not actual code, their existence is critical to designing loosely coupled object-oriented systems.

To understand coupling, consider the following class:

class CarFactory {
  public static ICar createCar (string type) {
    if (type.Equals("Volvo") {
      return new Volvo();
    } else if (type.Equals("BMW") {
      return new BMW();
  }
}
  public static Volvo createVolvo() {
    return new Volvo();
  }
  public static BMW createBMW() {
    return new BMW();
  }
}

The CarFactory class creates cars based on a string literal passed to the createCar() method. The method instantiates an object and returns a reference to the ICar interface. The createVolvo() and createBMW() methods, on the other hand, return specific classes. The caller of the createCar() method does not need to know about the type of object returned. The caller of the createVolvo() and createBMW() methods must be aware of the Volvo and the BMW class, respectively. Changes to the Volvo and BMW classes cannot be made without taking into consideration what would happen to the callers of createVolvo() and createBMW().

What if the caller of these methods called nonexistent methods on the Volvo and BMW objects? By exposing the Volvo and BMW classes to the user of the CarFactory class, we have tightly coupled that user to the Volvo and BMW classes. On the other hand, the createCar() method returns an interface, and therefore the caller deals only with the interface. You can create cars of different makes and models without necessarily making the user of the CarFactory class aware of all these classes; you make the user deal only with the interface instead of the actual class. This decouples the user of the CarFactory class from the inner workings of the CarFactory.

6.1.1 Differences between C# and Java Interfaces

C# and Java don't differ much in their concepts of interfaces. In both languages, an object can implement multiple interfaces, and interfaces can extend each other. There are, however, some aspects of C# interfaces that are different from Java interfaces. This section explores those differences.

Listings 6.1 and 6.2 show a sample interface and its implementing class.

Listing 6.1 The ILife Interface (C#)
namespace Interfaces {
  interface ILife {
    void Birth();
    void Death();
    void Happy();
    void Sad();
  }
}
Listing 6.2 Person Implementing the ILife Interface (C#)
using System;
namespace Interfaces {
  class Person : ILife {
       string name;

       public Person(string name) {
         this.name = name;
         Birth();
         Happy();
       }

    public void Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }

  public void Death() {
    Sad();
      Console.WriteLine("Death of {0}",this.name);
    }

  public void Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
  public void Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }

  static void Main(string[] args) {
         Person per = new Person("John");
         per.Death();
    }

  }
}

Following is the output of running Listings 6.1 and 6.2 in combination:

Birth of John
John is happy
John is sad
Death of John

Listing 6.3 shows the Java equivalent of these classes.

Listing 6.3 Java Equivalent of the ILife Interface
package test;
interface Life {
  void birth();
  void death();
  void happy();
  void sad();
}

package test;
public class Person implements Life{

  String name;

  public Person(String name) {
    this.name = name;
    birth();
    happy();
  }

  public void birth() {
    System.out.println("Birth of " ame);
  }
  public void death() {
    sad();
    System.out.println("Death of " ame);
  }
  public void happy() {
    System.out.println(name+" is happy");
  }
  public void sad() {
    System.out.println(name+" is sad");
  }
  static final void main(String[] args) {
    Person per = new Person("John");
    per.death();
  }
}

For simplicity, in C# we use the same name for the namespace of the interface and the implementing class. Similarly, in Java both the class and the interface are in the same default package. Table 6.1 compares the C# and Java code (keep in mind that the interface and the class are in the same namespace or package).

Let's test whether namespace changes in C# and package changes in Java affect the type of access modifiers allowed on the classes, the interfaces, and their methods. To do this in C#, you remove the Person.cs file from your current project. Click on the Project menu. Highlight the Person.cs file by clicking on its tab. Click on the Exclude From Project menu item. Now click on Add Component. Choose the Console File option from the pop-up. Name the file Alien.cs. Listing 6.4 shows the contents of this Alien.cs file.

Table 6.1. Code Comparison between C# and Java

C#

Java

The ILife interface and the Person class are in the same namespace. Note that the ILife interface must have either no access modifier or the public modifier.

The Life interface and the Person class are in the same package. Note that the Life interface must have either no access modifier or the public modifier.

The Birth() and Death() methods of the interface cannot have any access modifiers.

The birth() and death() methods of the interface are public and abstract.

The Birth() and Death() methods of the implementing class (Person) either must be public or must have explicit interface declaration (we discuss this feature of C# later in this chapter).

The birth() and death() methods of the implementing class (Person) must be public.

Listing 6.4 Alien.cs, a Class Implementing an Interface from a Different Namespace (C#)
using System;
using Interfaces;

namespace Interfaces1 {
  class Alien : ILife {
    string name;
    public Alien(string name){
      this.name = name;
      Birth();
    }

    public void Birth() {
      Console.WriteLine("Birth of {0}",this.name);
    }

    public void Death() {
      Console.WriteLine("Death of {0}",this.name);
    }
    public void Happy() {
      Console.WriteLine("{0} is happy",this.name);
    }
    public void Sad() {
      Console.WriteLine("{0} is sad",this.name);
    }

    static void Main(string[] args) {
      Alien per = new Alien("Theta-666");
      per.Death();
    }
  }
}

Running the combination of Listing 6.4 and Listing 6.1 results in the following output:

Birth of Theta-666
Death of Theta-666

Thus, in C# having the interface and the implementing class in different namespaces does not change the rules about access modifiers on the class and interface methods. In Java, however, having the interfaces and the implementing class in different packages requires that the interface be public if it is to be implemented by the class.

    [ directory ] Previous Section Next Section