As .NET developers, most of us are accustomed to object-oriented programming. Many of my articles and talks go far beyond the fundamental principles, but they are worth visiting for developers who need to brush up on OOP. In this article, I am going to avoid theory and focus on practical steps when building a small application. This is building toward an asynchronous programming post I will publish tomorrow.
Why should you care about OOP
When designing software, we will start out with requirements for what we are trying to build. It may be unnecessary to go the object-oriented route if you have simple requirements. I tend to start out separating parts into appropriate subsystem layers and deciding what belongs in separate, physical tiers. But let’s say you only need to create a console application that writes out a user’s latest tweets. If that’s the only requirement, there is no need to write anything other than a simple, procedural application. KISS: Keep It Simple Silly.
public static class Program
{
const string timelineUrl =
"http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=";
private static void Main(string[] args)
{
if (args.Length == 0)
return;
string userName = args[0];
WebClient client = new WebClient();
string xml = client.DownloadString(timelineUrl + userName);
XElement xmlTweets = XElement.Parse(xml);
var messages = from tweet in xmlTweets.Descendants("status")
select tweet.Element("text").Value;
foreach (var msg in messages)
Console.WriteLine(msg);
}
}
This simple program fits the requirement. If you call UserTweets.exe kodefuguru, my tweets will be printed to the screen. There are some out there who believe everything must be perfectly architected and designed, but you should allow requirements to drive your development. I could add error checking and logging routines, but if the only thing I need to do is create a console application that writes a user’s tweets to the screen, this program is fine the way it is. I’m done, move on to the next project.
Create classes
There are perfectly valid reasons to complicate this program with OOP. The primary reason is reuse, and I may anticipate using this functionality for another project. The first thing I notice is that other data is available when parsing the tweets. I’m going to group useful data into a class called Message. I am abstracting the actual twitter data. If I write programs that pull from other social media sources, this is useful for representing what I will use in my program. I can map data returned from the source to the class the program uses.
public class Message
{
public string Text { get; set; }
public string ImageSource { get; set; }
public string UserName { get; set; }
}
I probably want a class that handles the retrieval of these messages. If I needed to do more than one at a time, reusing a class is preferable to copying and pasting code. The former encapsulates the functionality so that if the details change, I only need to change the implementation in one place.
public class Twitter
{
const string timelineUrl =
"http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=";
private string userName;
public string UserName
{
get { return userName; }
set
{
if (String.IsNullOrWhiteSpace(value))
throw new ArgumentNullException();
userName = value;
}
}
public Twitter(string userName)
{
this.UserName = userName;
}
public Message[] GetMessages()
{
WebClient client = new WebClient();
string xml = client.DownloadString(timelineUrl + this.UserName);
XElement xmlTweets = XElement.Parse(xml);
var messages = from tweet in xmlTweets.Descendants("status")
select new Message
{
ImageSource = tweet.Element("user").Element("profile_image_url")
.Value,
Text = tweet.Element("text").Value,
UserName = tweet.Element("user").Element("screen_name").Value
};
return messages.ToArray();
}
}
The twitter class has the functionality for calling twitter, and I only need to instantiate one to make the calls from another class. You may also notice that I required UserName to be passed in the constructor, and it throws an exception if it is null or whitespace. The username is required, and encapsulation elegantly wraps up that requirement.
The alternative to encapsulation is to publicly expose the field. If you design classes in this manner, you will find that there is no easy way to control changes to an object’s memory. It is better to encapsulate a field into a property.
The program now looks like the following.
private static void Main(string[] args)
{
if (args.Length == 0)
return;
string userName = args[0];
Twitter twitter = new Twitter(userName);
var messages = twitter.GetMessages();
foreach (var msg in messages)
Console.WriteLine(msg.Text);
}
Inheritance and Polymorphism
This handles the capabilities of twitter, but I may want to access other social media sources such as Facebook or Google+. We can solve this problem by using inheritance. If you look at the GetMessages method, it’s not too difficult to determine what will be different between services. There are two major pieces: the URL and the mapping.
Interfaces could be used, but there is shared implementation. I will create a SocialMediaReader abstract class to encapsulate the things that stay the same. I changed Message[] to IEnumerable<Message> since client classes should only read the messages.
public abstract class SocialMediaReader
{
protected abstract Uri GetUri();
protected abstract IEnumerable<Message> ParseData(string data);
public IEnumerable<Message> GetMessages()
{
WebClient client = new WebClient();
string data = client.DownloadString(GetUri());
return ParseData(data);
}
}
I moved the Twitter specific functionality to the Twitter class, which inherits from SocialMediaReader.
public class Twitter : SocialMediaReader
{
private string userName;
public string UserName
{
get
{
return userName;
}
set
{
if (value == null)
throw new ArgumentNullException();
userName = value;
}
}
public Twitter(string userName)
{
this.UserName = userName;
}
protected override Uri GetUri()
{
return new Uri("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name="
+ this.UserName);
}
protected override IEnumerable<Message> ParseData(string data)
{
XElement xmlTweets = XElement.Parse(data);
var messages = from tweet in xmlTweets.Descendants("status")
select new Message
{
ImageSource = tweet.Element("user").Element("profile_image_url")
.Value,
Text = tweet.Element("text").Value,
UserName = tweet.Element("user").Element("screen_name").Value
};
return messages.ToArray();
}
}
Now the program can use any class that inherits from SocialMediaReader. This makes it easy to switch out social media providers in the future.
static void Main(string[] args)
{
if (args.Length == 0)
return;
string userName = args[0];
SocialMediaReader reader = new Twitter(userName);
var messages = reader.GetMessages();
foreach (var msg in messages)
Console.WriteLine(msg.Text);
}
This works because the Twitter class is a SocialMediaReader, and that’s all we need. Use inheritance to represent an *IS* relationship. Notice that the Twitter class still contains a UserName property. Use composition to describe a *HAS* relationship. The Twitter class is a SocialMediaReader and it has a UserName property.
Reuse
I can easily take these classes and use them in another program. I’ve created a simple WPF application that does just that.
The view is nothing fancy, just a TextBox, Button, and ListBox.
<Window x:Class="Eventing.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Name="SearchText"/>
<Button Name="SearchButton" Grid.Column="1" Content="Search"
Click="SearchButton_Click"/>
<ListBox Margin="0,10,0,0" Name="listBox" Grid.ColumnSpan="2" Grid.Row="1" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="132">
<Image Source="{Binding ImageSource}" Height="73" Width="73"
VerticalAlignment="Top" Margin="0,10,8,0"/>
<StackPanel Width="370">
<TextBlock Text="{Binding UserName}" Foreground="#FFC8AB14"
FontSize="28" />
<TextBlock Text="{Binding Text}" TextWrapping="Wrap"
FontSize="24" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
I decided not to dive into presentation patterns in this article, so I am using code behind to represent the SearchButton’s click functionality.
private void SearchButton_Click(object sender, RoutedEventArgs e)
{
SocialMediaReader reader = new Twitter(SearchText.Text);
listBox.ItemsSource = reader.GetMessages();
}
Since I had already created the classes necessary for this application, it was a simple matter of calling them. I did not need to copy and paste code, and if I enhance my class library other apps that reference the library will benefit directly from my efforts.
There is one difference between the console application and the WPF application that will require me to make changes. When you click the search button, the application is unresponsive until it returns. Tomorrow, I will go about fixing this problem in C# 4, and then I will show how C# 5 makes it even easier to accomplish.