Kuro5hin.org: technology and culture, from the trenches
create account | help/FAQ | contact | links | search | IRC | site news
[ Everything | Diaries | Technology | Science | Culture | Politics | Media | News | Internet | Op-Ed | Fiction | Meta | MLP ]
We need your support: buy an ad | premium membership

[P]
Working with Factories

By e8johan in Technology
Fri Sep 12, 2003 at 09:35:12 AM EST
Tags: Software (all tags)
Software

A quick guide for using the factory pattern when implementing loading and saving functionality in an application.

The reason for writing this text is to show how patterns can be applied to every day problems and making programming easier. I also want to promote object oriented thinking as I feel that it is not as adopted as it deserves to be in the open source world (or in the closed source world either).


This article shows how to apply a pattern to an everyday problem. For more in-depth information concerning the factory pattern, look here (wikipedia). Even more pattern information can be read in the GoF-book (the Gang of Four), the "pattern bible". When discussing further reading, the interested reader can also continue by looking at the abstract factory pattern.

What is a pattern? It is what lies beyond the code. Try to look above the code at the structure of interconnected instances of classes. This is the pattern. I like to see it as the next level of abstraction.

The Problem

We want to be able to save and load our data, the document, our settings, whatever. The file formats may differ and we may be able to load from ten formats and save to seven formats of which only five can be read from. While doing this we are interested in finding a nice object orientation to get a good structure.

I restrict this example to loading from files, but this approach can be used to handle different sources of data, etc.

The Code

First, let us declare a document class. This class acts as a data container but also hold methods to handle user interaction and such. Through out this text I will use C++ as implementation language, but look at the code as pseudo code. The concepts can be implemented in any other language - but it is nice if it supports object orientation.

class Data
{
public:
  Data();
  ~Data();

  int value();
  setValue( int );

  void process();

private:
  int value;
};

This class is only an example. All that it does is that it holds a value. It also has a method called process() that does something to the value.

Now for the problem to be solved. We want to be able to save and load the Data class in a number of different formats, or encodings. Let us start with the loading.

Which Pattern to Use

There are two major factory patterns (there are others too, and even more creational patterns): The Factory Pattern and the Abstract Factory Pattern.

The factory pattern works as this. The creator, i.e. the function wanting to create an instance of the worker object (the object performing a task) class, calls the factory method which in turn creates the requested instance and returns it.

The abstract factory pattern defines an abstract base class (i.e. an interface) with a number of factory methods. A famous example is the cross platform user interface framework with an abstract factory that can create buttons, labels, text boxes, etc. The abstract base factory is then inherited for each platform implementation where the factory methods are implemented.

How is the abstract factory used then? The creator creates an instance of the inherited factories but only uses the abstract factory interface defined by the abstract base class. It then uses the factories of the instance to build its objects.

We will use the factory pattern. Why? Since we cannot save in all formats and cannot load from all formats, we need two factory methods: one for loading and one for saving. If we would have been able to load and save in every format an abstract factory pattern would have been and option - but the factory pattern would not have been excluded.

The Solution

We begin by defining an interface class (i.e. a class without any non-virtual methods and no data) for our worker class.

class DataLoader
{
public:
  virtual Data *load( char *fileName );
};

This provides us with an interface to a data loader. All it has is a method called Load that load the information and returns a pointer to a Data (or null if it fails).

Now we can implement a number of loaders:

class BigEndianLoader : public DataLoader;
class LittleEndianLoader : public DataLoader;
class UniCodeTextLoader : public DataLoader;
class CompressedLoader : public DataLoader;

I do not bother implementing these loaders, but I can summarize what they must do. First they open the file and read the data from the stream. Then, if the first stage succeded, they create an instance of the Data class and use the public interface to set the state of the object according to the loaded data. Finally, they return a pointer to the newly created object. Of course they must also close the file, clean up any dynamically allocated memory, etc. But that is details that this text is not concerned with.

To handle these loaders we need a loaded factory class. The reason to creating a class and not a single function is that a class can handle loading of plug-ins and such in a real application. This class could benefit from being implemented as a singleton, or the information concerning, for example, plug-ins could be allocated statically. This is up to the implementor.

class DataLoaderFactory
{
public:
  DataLoaderFactory();
  ~DataLoaderFactory();

  DataLoader *createLoader( char *fileType );
};

Why is not an enumerated type used for the file types? Since the factory can handle plug-ins or any other technique to handle unknown file formats the number of managed formats are not known. One way to handle this is to add a method that returns a list of all types that can be handled. For example, when showing a file loading dialog one could extract the accepted file formats from the factory and the feed the factory the user's choice from the dialog.

So, how is this used? Let me show you a small example.

Data *someClass::testMethod()
{
  DataLoaderFactory f;
  DataLoader *dl;
  Data *d;

  dl = f.createLoader( "type" );
  if( !dl )
    throw( someException() );

  d = dl->load( "/home/joe/file.ext" );
  delete dl;

  return d;
}

Nice and easy. First, we try to find a loader for the file type with the extension "ext", then we load the file using the loader. If the file format is unknown we react at the error by throwing an exception.

There may arise issues over how memory allocation and such is handled. This is a language dependant (non-pattern) issue. In the C++ recommentation I would choose to make the factories work as the new keyword, i.e. the calling function is responsible for deleting it.

So, I was writing about loading and saving. What about the saving you may ask? Just write your own DataSaver interface class, a DataSaverFactory and they some data savers. It works the same way, just that the method of the DataSaver interface aught to be save( const Data& ) instead of load.

Conclusions

There are many patterns available that make it easy to design powerful and structured solutions. By learning patterns and applying them to everyday problems slick solutions are achieved without breaking a sweat.

The example above is small, but not nave. It utilizes a pattern to implement a common problem. As discussed in the text several patterns may apply to one solution, this is where the developer's experience comes into work. Any experienced developer working with object orientation should know patterns; How to use them and when.

For the programming reader without any experience of object orientation I hope that this article gives a glimpse into what the abstractions that object orientation provides can be used for. Remember that patterns are not bound to one particular programming language but represents a concept that can be implemented in any environment.

Sponsors

Voxel dot net
o Managed Hosting
o VoxCAST Content Delivery
o Raw Infrastructure

Login

Poll
Patterns?
o Good 44%
o Bad 24%
o Don't know 31%

Votes: 45
Results | Other Polls

Related Links
o here
o GoF-book
o abstract factory pattern
o creational patterns
o interface
o singleton
o Also by e8johan


Display: Sort:
Working with Factories | 71 comments (21 topical, 50 editorial, 0 hidden)
More info (1.20 / 10) (#3)
by veldmon1 on Wed Sep 10, 2003 at 08:27:17 AM EST

There needs to be more background information included so those of us not familiar with these two particular users can make a more informed opinion as to which one qualifies as the pitcher or catcher.

Thanks (3.33 / 3) (#34)
by ph317 on Thu Sep 11, 2003 at 03:56:07 AM EST


Yet more proof that excessive OO thinking leads to convoluted unneccesary overdesign and overcoding.  I mean, wtf, even if you want to do this OO-style in C++, just make your Data class, and make some methods for external importing and exporting and be done with it, jeez.

What about... (5.00 / 1) (#36)
by e8johan on Thu Sep 11, 2003 at 04:05:26 AM EST

...extendeding your hack with support for more filetypes, etc. I find your attitude towards software development being nave.

[ Parent ]
Re: What about... (none / 0) (#67)
by ksandstr on Tue Sep 16, 2003 at 08:23:12 PM EST

How many years' worth of real-world experience did you say you had again?

Not that I take any particular kind of issue with your article, but your statement reeks of inexperience, or of an unhealthy love affair with the whole "must have OO" wankage thing anyway. Particularly the bit where you use the word "hack" as a derogatory term for anything that isn't designed to be so completely future-proof and invincible that it should be carved into fucking slabs of marble the first time around.


Fin.
[ Parent ]

8 years... (none / 0) (#68)
by e8johan on Wed Sep 17, 2003 at 09:00:25 AM EST

...as a software developer, almost two years full time. If you want the effective time, about four years since I worked while completing my studies.

Much of the code produced in the real world is what I concider hacks. I myself do this all too often too.

The lesson that I have learned is that specs changes and software grows old and needs to be maintained and adapted to the unknown needs of the future. Though intelligent object orietation of the basic framework to be used the small hacks can be made future compatible. For example, I treat test data in the form of data signals (pressure sampled at 10kHz). The interface of the signal class hasn't changed (other than that it has been expanded, yes I've had to recompile, but not too much rewriting) sin 1997 when I did the first proper rewrite.

I am not an OO zealot, I tend to look at myself as pragmatic. But! Since the article deals with a pattern implemented as an OO-structure I speak of it fondly. Patterns and OO is not a replacement of proper knowledge and experience, but an experienced developer can make use of them (at least it extends the vocabulary and reduces the need of explaining many implementation details).

[ Parent ]

At first... (none / 0) (#57)
by endeavor on Fri Sep 12, 2003 at 03:42:54 PM EST

... I thought the same thing. Then I read it again and now I think it's pretty interesting. Yes, in a lot of situations it's overkill. But if you've got a huge platform versus data-type matrix then this could be very useful. If the matrix is large enough then your approach could get very messy when switching platforms, while the factory paradigm should remain mostly static.

[ Parent ]
Entirely correct (none / 0) (#62)
by Confusion on Sat Sep 13, 2003 at 08:58:43 AM EST

For small programmes, design patterns are often overkill. Hell, even thorough use of OO is sometimes overkill. But small programmes tend to grow. Unless you are very sure the programme wil stay small, starting out with a thorough design, including all blessings of OOP and design methods, will make your job infintely easier later on.
--
Any resemblance between the above and reality is purely coincidental.
[ Parent ]
Patterns (none / 0) (#60)
by djotto on Fri Sep 12, 2003 at 09:40:21 PM EST

are as much about human language as they are about code.

They're an attempt to expand the vocabulary of software design, to move it to a higher level of abstraction. Once you and I both understand the concept of (for example) a Singleton, we can save a lot of effort in understanding each other's code, or discussing software development.

That we get good, general-purpose skeletons along with the vocabulary is just a bonus.

[ Parent ]

This is a good article. (4.60 / 5) (#42)
by megid on Thu Sep 11, 2003 at 07:25:17 AM EST

I know not everyone agrees with OO in general (there are good arguments that OO is not as good as hyped) and with patterns in special (mostly because they are poorly understood -- they are not even bound to the OO world!), but whats this voting shit here?

We had the same one on the ReiserFS article a few weeks back. Users are voting this down... why? Because it doesnt match their predetermined world view? Because they think its incomplete? Because they dont like the direction the author is taking? Because they dont take any effort to understand what the author wants to explain?

This is a good article. I am of slightly different opinion from the author; in some points, I disagree. Nonetheless, this is the kind of article K5 needs. Not some more "but Saddam/Bush did blablabla". Or fiction. Or... whatever.

Have a good technical day.

--
"think first, write second, speak third."

It's a good article (none / 0) (#43)
by jdy on Thu Sep 11, 2003 at 08:38:25 AM EST

and this draft is signficantly better than the previous versions, but the code itself is poor.  Some of the code isn't even syntactically valid, as I've mentioned before.

[ Parent ]
And each time... (none / 0) (#53)
by e8johan on Fri Sep 12, 2003 at 12:44:33 AM EST

...I've pointed out that the code isn't supposed to compile. (link 1, link 2)

[ Parent ]
A few things... (5.00 / 3) (#54)
by Pseudonym on Fri Sep 12, 2003 at 01:10:57 AM EST

I think that no discussion of abstract factories is complete without a discussion of registries. A registry is a mapping of a key type to an abstract factory. This is used to create objects by name (e.g. if the key is a string) or some other identifier (e.g. OID in ASN.1, UUID in COM etc).

Another thing to keep in mind is that factories don't have to just create objects. Factories can be used, for example, to manage flyweights, that is, immutable objects which are created once and then shared should an identical object be needed later.

Also, they don't actually have to create what they advertise. For example, consider a factory which creates query nodes for some kind of database. The factory which creates "Not" queries can inspect its children, see if it's a "Not", and remove the double negation.

Factories also work in non-OO languages, even declarative languages. See the Haskell wiki entry on FactoryFunction for some ideas.

Oh, one more comment on C++ style. Never, ever, pass around unmanaged pointers unless you intend them to be unmanaged. This, in particular, should be a red flag:

DataLoader *createLoader( char *fileType );

In this prototype, on the other hand, there is no question about deleting objects:

std::auto_ptr<DataLoader> createLoader( char *fileType );


sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
Two things (3.00 / 1) (#55)
by pdrap on Fri Sep 12, 2003 at 01:57:49 PM EST

  1. A pattern is useful for describing any kind of design problem, and in fact was not invented for OOP. Being a very good idea, the pattern as a form of writing down design problems and their solutions was wisely adopted by the OOP community.
  2. When I make interfact classes, I call them abstract base classes. You might find that the definition of DataLoader works better in C++ if you change it to this:

class DataLoader {
public:
   virtual Data *load (char *fileName) = 0;
};

Naturally, having my own stubborn views of proper code layout, I changed that too. :-)


Abstractions (5.00 / 1) (#61)
by David McCabe on Sat Sep 13, 2003 at 02:47:41 AM EST

Mabye I'm a weirdo, but when I see patterns in my code, it usually means I'm not using big enough abstractions.

If I found myself writing what you've just written, I'd immediately cook up a macro to generate it all for me and stick it in my repository of generally useful macros, so I could then just say:

(def-loader-function (for-type) (file)
   my-code-to-load-from-that-file)

and be done with it. The macro would define a function, 'load-type', given the argument 'type', so that I could then load something by saying:

(let (my-object (load-type somefile)) ...)

easy, huh? Patterns are usually a Bad Thing. If you find yourself writing what is basically the same code multipal times, write a function, class, or macro do to it for you.

I think I read somewhere (can somebody confirm these exact figures?) that 18 of the 24 patterns in /Design Patterns/ are invisible in Lisp, simply because it has the facilities to abstract them.


Factory is somewhat a strongly-typed thing (none / 0) (#69)
by WayneConrad on Sun Nov 02, 2003 at 02:02:30 PM EST

Some of what you're noticing is the difference between a strongly typed language (C++) and a dynamically typed language with the ability to define functions at runtime (Lisp, Ruby, etc.).  Some of the common patterns are ways to get some air past the stranglehold that strong typing puts around the programmer's neck.  Factory is one of those patterns.  You've shown how the factory pattern isn't needed in Lisp.  Since Ruby is my language-de-jeur, I'll show how Factory isn't needed in Ruby, another dynamically typed language.

For those who haven't heard the term "dynamically typed:" In a strongly typed language, the programmer supplies the compiler with type information for variables so that the compiler can check that only operations that are valid for each variable's type are performed.  In a dynamically typed language, the programmer supplies no type information and just treats the variable as an integer, or string, or whatever the programmer thinks is stored there.  The language still checks that only valid operations are performed, but that check is done at runtime, not at compile time.

Strongly typed (C++):

  int v;
  v = 2;
  cout << v + 1 << endl;  / prints "3"
  v = "foo";              
/ error!

Dynamically typed (Ruby):

  v = 2
  puts v + 1     # prints "2"
  v = "foo"      # it's ok, now v references a String
  puts v.length  # prints "3"

This example only shows how dynamic typing doesn't stop you from storing whatever you want in any variable.  It is NOT how dynamic typing is usually used: Reusing a temporary for a different purpose is a Bad Code Smell; the good programmer will use a different name for a temporary that has a different purpose rather than reusing an existing temporary just because it's there.

Programmers can come to prefer strong typing despite the restrictions it places upon the coder because strong typing allows the compiler to do an elementry level of testing for you.  The benefits of strong typing (elementary automatic testing) are worth the drawbacks until you start doing rigorous automatic testing, at which point it's better to get a language that stops arguing with you about what type a thing is.  If your tests are covering all the conditions that the compiler's strong typing would catch, then you're paying for something you no longer need and should consider the benefits of a dynamically typed language.

I'm not a Lisper (although I do have Graham's "Common Lisp" on my book shelf, and I promise to read it really soon.  Does that count?), so I'll show how a Ruby coder might do the same things as the author did in his example, but without as much fuss.

One of the cool things about Ruby is that classes are "first class": They are just objects you can store and pass around like any other object at runtime.  This is different than C++, where classes belong only to the compiler.  So let's make a hash to map file types to loader class names.  I'll just make up some silly file types, since I don't know what they might be:

  Loaders = {
    "be" => BigEndianLoader,
    "le" => LittleEndianLoader,
    "ut" => UnicodeTextLoader,
    "cl" => CompressedLoader,
  }

Now, to use it, just use the hash to get the name of a loader class, create an instance, and tell it to load:

  loaderClass = Loaders[file_type]
  raise "Unknown file type" if loaderClass.nil?
  loaderClass.new.load("/home/joe/file.ext")

Other than the declaration for the Data class, this is everything that the author's example did.  There is no need for factories when you can throw class names around.

Also, we don't have to declare our loaders to be derived classes of some base class or interface -- all that matters is that each loader has a 'load' method.  Rather than the "is a" polymorphism of a strongly typed language, we have "looks like a" polymorphism.


[ Parent ]

dynamic != static (none / 0) (#70)
by aurelien on Tue Nov 04, 2003 at 07:28:03 AM EST

Strong typing is not the complement of dynamic typing, those are independant notions.

strong typing <-> not weak typing
dynamic typing <-> not static typing
who uses that ?
[ Parent ]

So I'm told... (none / 0) (#71)
by WayneConrad on Thu Nov 06, 2003 at 09:38:04 PM EST

I've had some great emails from a gentleman who took the time to fill me in on the facts (most of which I got wrong, it turns out).  I'd like to see a full article about typing -- there's a lot more to it than I knew.


[ Parent ]
Patterns used and abused (a rant) (4.00 / 1) (#63)
by israfil on Sat Sep 13, 2003 at 09:26:03 AM EST

What drives me nuts these days, with 2-year Java "experts" running around, is how such a basic concept can be ruined.  I like how you did the above, but if you look carefully, you used 1 factory to provide N implemntations.  (Disclaimer - this rant is not directed at the author of the above article)

What I often find in a lot java and C++ frameworks is this notion of having an implemnetation class, and for each implementation you write a factory.  So you end up with a factory-lookup class (which I shall <gasp> call a factory-factory) which finds the right factory, which finds the right implementation class.  Only these factories usually have only one method, which provides an implementation to an interface, and you have to register the factory anyway, so it ends up being "Yet Another Unnecessary Abstraction" (YAUA)

This kind of thing is politing frameworks out there, and comes, I believe, from a doctrinaire attitude towards patterns in general.  People are no longer thinking critically about their code - they are 'finding patterns' to solve their problems.  Only this isn't what patterns were meant to do (or shouldn't be).  Patterns provide a common language to describe solutions.  They provide a vehicle for communicating meaning.  

But in McLuhanesque style, the medium has become the message.  Many people with a year of java coding experience are called upon to design complex systems, with no experience for the interactions of such high-capacity systems, no experience with distributed systems, and other such issues, begin to panic when they must design such.  They then go to their favorite Gamma et. al. book and pull out some patterns related to their high-level projects, then find some piece of sample code on the net, or a framework they don't fully comprehend, and patch it all together.  They then wonder why things don't scale, can't fit together, and why people with 5-10 years of SD and architecture experience scream when they're brought in to save the projects.

I like the article above, in that it is a reasonable examination of the use of a factory.  I hope the readers (and I'm banking on a slightly higher competance of K5 readers counting for something here) will take this and go forth, not replacing judgement with patterns, experience with "the latest fad", and thoughtful architecture with guru-platitudes. (sigh)

.
i. - this sig provided by /dev/arandom and an infinite number of monkeys with keyboards.

Overhyping of patterns (none / 0) (#65)
by Three Pi Mesons on Sat Sep 13, 2003 at 03:44:55 PM EST

I totally agree with your comments regarding patterns. I think that the concept of a "pattern language" (in architecture or programming) is a compelling one, but the way we now think of software patterns has some major problems.

Firstly, the term "pattern" is debased through overuse. If everything becomes a pattern, then you may as well not use the word. It should be reserved for something a bit more special - none of this guff about using the "for-loop pattern".

Secondly, the Gang-of-Four patterns are mostly oriented towards solving problems that exist in C++. Mark-Jason Dominus points out that the "Iterator" GoF pattern is, in Perl, just the "foreach" statement (and similar constructs exist in other languages). Someone - possible Paul Graham, I can't remember - went through all of the GoF patterns, pointing out that in Lisp they are almost all builtins or common idioms. This wouldn't be so much of an issue if more people were a bit more critical of Gamma et al.

It's a pity that thinking in terms of patterns isn't more mainstream in user interface design, because that's where the approach could really shine. It's a lot closer to what Christopher Alexander was talking about originally in architecture: mediating between the designer and the user. Low-level patterns don't really capture that idea, because they're not user-visible - so much of the motivation for pattern languages has gone.

:: "Every problem in the world can be fixed with either flowers, or duct tape, or both." - illuzion
[ Parent ]

OO is the best path to suicide for the ignorant (4.50 / 6) (#64)
by israfil on Sat Sep 13, 2003 at 09:49:12 AM EST

Some of the comments around this one point to the badness of OO, and its over-hypedness, etc.

OO is not about simplifying code.
OO is not about reuse.
OO is not about making software easier.
OO is not about reducing up-front development costs.

OO is about managing complexity (with some overhead and up-front cost) and about creating software that maps more clearly to a real-world problem.  Period.  It's also about style.

It is usable for small programs, but is not "better" or "worse" for them.  You may get the benefit of your code mapping to the thing being modelled even in small code.  Usually, however, the code is non-complex enough that you can just use simple functional or structural programming.

Where OO works really well, is if you have large enough code-bases.  You can vastly reduce your code-base, partition your functionality so that fewer people need to negotiate when they make changes (Clearer boundaries and interfaces) and you can organize your functionality more rationally than an equivalently complex functional or structural code-base. (the latter is more the style thing, since it's simply my opinion)

Can you get the benefits of OO elsewhere?  Sure.  I've seen good C done in a way that gets much of the benefits of OO.  However, the point of an OO language is that it supports the structures and approaches that one has to implement in C to do it.  OO is a short-cut, or pre-implementation of some of these approaches.

Objective-C is a great example, by the way.  It is C, but it's given a run-time, a message-passing system, and a pre-compiler that allows you to group meaningful and related data and code together in modules.  It all compiles down to C, but it's more dynamic, because of the messaging system is used for almost all method calls. (think C with smalltalk-style object extensions)  It gets a 25% hit for the hash-based messaging system, but you can optimize that out by hard-coding the function call for those 20 methods that are used the most, and end up within 5% of an equivalent C code.

That dynamism means that creating plugin-oriented architectures (What many call factories ;) is almost the native paradigm, as well as many other ways of doing things.  This really encouragees good reuse, if the base-class library is well implemented.  Having programmed NeXT systems (Apple now uses this system in OS-X) I found it amazingly refreshing, because they made massively-reusable frameworks - mostly reusable because they were highly configurable.  You didn't sub-class a GUI widget, you instantiated it with properties.  JavaBeans (not EJB) was meant to replicate this, but no one bothered to use them this way. (sigh)

Did performance get hit?  Sure!  However, coding time, and more importnatly, maintainence time went down dramatically.  Total cost and management burden of the software went down.

Modern Java examples really suck at this, mostly because people came into the Java world from arenas with less experience with good-OO and began to implement really sucky examples. Sun deserves strong blame in this.  Junior people followed these examples and we have disgusting code all over the Java world.  

If you want to do O-O, you can hurt yourself far far worse in some ways than with structured or functional programming, because you can really fool yourself into believing that you are making better software.  If you are well-versed at solid OO approaches, then you can do miracles beyond what I've ever seen in other approaches.

It's probably obvious to say that better people make better software, but with OO, better people make vastly better software, and worse people make far far worse software.

It's a pity there's so much garbage out there.

Oh, and P.S.  J2EE, and most specfically EJB's are some of the worst examples of OO over-architecture with little benefit that I've ever had the misfortune to see - and my resume says I'm an expert in those technologies.

P.P.S.  for decent C++ approaches, check out the QT framework.  It's a GUI framework that uses a nice sockets/plugs system for interaction between components and data and controllers, with a nice meta-object compiler built in.  Best C++ I've seen.

.
i. - this sig provided by /dev/arandom and an infinite number of monkeys with keyboards.

re: Gang of Four ... pattern bible (none / 1) (#66)
by codemonkey_uk on Sat Sep 13, 2003 at 07:46:46 PM EST

Many people seem to think that the "Gang of Four" (GoF) book is the only, or definitive, book on software patterns. It is a good book, we were told, but it is 10 years old now, and the industry, and it's understanding of design patterns has moved on.


---
Thad
"The most savage controversies are those about matters as to which there is no good evidence either way." - Bertrand Russell

Working with Factories | 71 comments (21 topical, 50 editorial, 0 hidden)
Display: Sort:

kuro5hin.org

[XML]
All trademarks and copyrights on this page are owned by their respective companies. The Rest 2000 - Present Kuro5hin.org Inc.
See our legalese page for copyright policies. Please also read our Privacy Policy.
Kuro5hin.org is powered by Free Software, including Apache, Perl, and Linux, The Scoop Engine that runs this site is freely available, under the terms of the GPL.
Need some help? Email help@kuro5hin.org.
My heart's the long stairs.

Powered by Scoop create account | help/FAQ | mission | links | search | IRC | YOU choose the stories!