I'll give a real world example. I have an abstract class call cSourceControl. Then I have a class for Visual Source Safe call cVSS and one for Subversion called cSVN, both inherited from cSourceControl.
Now the abstract class has a bunch properties and methods (yes, with code) that the inheriting classes can just use. The abstract class also defines a bunch of abstract methods that an inherited class must implement:
public abstract DataTable getFiles(string path, string filter, DateTime since, bool filterAuthorByLastCheckin, bool expandAll, bool onlySinceBranched);
public abstract DataTable getFiles(string path, string filter, DateTime since, string lastUser, bool filterAuthorByLastCheckin, bool expandAll, bool onlySinceBranched);
public abstract long getFile(string sFileName, string sVersion, string sLocalFileName);
public abstract DataTable getFileVersions(string sFileName);
public abstract DataTable getDirectories(string path, bool expandAll);
public abstract DataTable getChangedFiles(string path);
public abstract DataTable GetFileLogRevision(string path, string revision);
public abstract DateTime getBranchStartDateTime(string sBranch);
From this you can tell that a SourceControl class will need each of these, but the way it looks in cVSS is very different than how it looks in cSVN. Another payoff is that at runtime I can select VSS or SVN. A simplified code snip might be:
cSourceControl sc;
if(usingVSS)
sc = new cVSS();
else
sc = new cSVN();
DataTable dtFiles = sc.getChangedFiles("myproject/branches/5.1/");
etc.
Now when my customers start asking for Git or SourceGear, I just have to create those classes and change very little else.